You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1500 lines
58 KiB
C++

/**
* @file scene.cc
*
* @auhtor Remus Claudiu Dumitru <r.dumitru@jacobs-university.de>
* @date 13 Feb 2012
*
*/
//==============================================================================
// Includes
//==============================================================================
#include "model/scene.h"
#include "model/graphicsAlg.h"
#include "model/util.h"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <boost/algorithm/string.hpp>
#include <sys/stat.h>
#include <errno.h>
#include <limits>
#include <iomanip>
#include <fstream>
#include <sstream>
using namespace std;
//==============================================================================
// Static fields initialization
//==============================================================================
const double model::Scene::PRECISION = 0.0;
const double model::Scene::RAY_DIST = 2.5;
const double model::Scene::PATCH_DIST = 1.0;
const double model::Scene::WALL_DIST = 10.0; // must be bigger than RAY_DIST
const double model::Scene::MIN_EDGE_COV = 0.60;
//==============================================================================
// Implementation
//==============================================================================
model::Scene::Scene(const IOType& type,
const int& start, const int& end,
std::string dir, const bool& scanserver,
const int& maxDist, const int& minDist,
const PlaneAlgorithm& alg, const int& octree, const double& red,
const vector<Pose6d>& poses)
{
if (!quiet) cout << endl << "== Creating scene..." << endl;
if (!quiet) cout << endl << "== Reading scans..." << endl;
// create an output directory for planes and for images
if (makeDir(dir + "img/")== false) {
throw runtime_error("failed to create directory " + dir + "img/");
}
// copy class fields here
this->poses = poses;
// identity matrix
double id[16];
M4identity(id);
// begin the plane detection
// Scan::dir = dir;
// Scan::readScans(type, start, end, dir, maxDist, minDist, false);
Scan::openDirectory(scanserver, dir, type, start, end);
if(Scan::allScans.size() == 0) {
cerr << "No scans found. Did you use the correct format?" << endl;
exit(-1);
}
int nrPlanes = 0 , currScan = start;
// for(vector <Scan*>::iterator scan = Scan::allScans.begin(); scan != Scan::allScans.end(); ++scan) {
for(ScanVector::iterator scan = Scan::allScans.begin(); scan != Scan::allScans.end(); ++scan) {
// prepare for plane detection
(*scan)->setRangeFilter(maxDist, minDist);
(*scan)->setReductionParameter(red, octree);
// scan->setSearchTreeParameter(nns_method, cuda_enabled);
(*scan)->toGlobal();
(*scan)->transform(id, Scan::ICP, 0);
// read the pose of the current scan
pair<Vector3d, Rotation3d> pose;
const double* pt = (*scan)->get_rPos();
const double* rot = (*scan)->get_rPosTheta();
pose.first.x = pt[0];
pose.first.y = pt[1];
pose.first.z = pt[2];
pose.second.x = rot[0];
pose.second.y = rot[1];
pose.second.z = rot[2];
if (!quiet) cout << "\n== Starting plane detection for scan " << currScan << " @ pose ("
<< pose.first.x << " " << pose.first.y << " " << pose.first.z << " "
<< pose.second.x << " " << pose.second.y << " " << pose.second.z << ")..." << endl;
// apply hough transform
Hough hough((*scan), true);
nrPlanes += hough.RHT(); // TODO call proper algorithm
if (!quiet) cout << "** Detected " << nrPlanes << " planes so far" << endl;
// loop over all planes and fill in the data
for (vector<ConvexPlane*>::iterator convexPlane = hough.planes.begin();
convexPlane != hough.planes.end(); convexPlane++)
{
// get the nomrla and the point of application of the normal
double tempNormal[3], tempOrigin[3];
(*convexPlane)->getNormal(tempNormal, tempOrigin);
// the normal and application points using our datastructures
Vector3d normal(tempNormal[0], tempNormal[1], tempNormal[2]);
Point3d origin(tempOrigin[0], tempOrigin[1], tempOrigin[2]);
// fill in the convex hulls
vector<double> doubleHull = (*convexPlane)->getConvexHull();
vector<Point3d> pointHull;
for (vector<double>::iterator point = doubleHull.begin(); point != doubleHull.end(); point += 3) {
pointHull.push_back(Point3d(*(point+0), *(point+1), *(point+2)));
}
Plane3d planeToAdd(origin, normal, pointHull);
// add the planes to the vector
if (planeToAdd.isHorizontal() || planeToAdd.isVertical()) {
this->planes.push_back(planeToAdd);
}
else if (!quiet) {
cout << "** Discarding oblique plane centered at "
<< planeToAdd.pt.x << " " << planeToAdd.pt.y << " " << planeToAdd.pt.z
<< endl;
}
}
// keep the pointer to the points
DataXYZ points = (*scan)->get("xyz reduced");
unsigned int points_size = points.size();
// const vector<Point>* points = (*scan)->get_points();
for (unsigned int i = 0; i < points_size; ++i) {
// XXX get_points returns a pointer
Point3d toPush(points[i][0], points[i][1], points[i][2]);
toPush.translate(pose.first);
toPush.rotate(Point3d(0.0, 0.0, 0.0), pose.second);
this->points.push_back(toPush);
}
// contains the current scan number
currScan++;
}
// to be used when constructing the octree
this->octTreePoints = new double*[this->points.size()];
// go over all points from all the scans
for (unsigned int i = 0; i < this->points.size(); ++i) {
double x = this->points[i].x;
double y = this->points[i].y;
double z = this->points[i].z;
// add the points to the array to be added to the octree
this->octTreePoints[i] = new double[3];
this->octTreePoints[i][0] = x;
this->octTreePoints[i][1] = y;
this->octTreePoints[i][2] = z;
}
// Create the octree and fill it in.
if (!quiet) cout << endl << "== Building OctTree with " << this->points.size() << " points ..." << endl;
this->octTree = new BOctTree<double>(this->octTreePoints, this->points.size(), octree);
//this->_octTree->init();
this->nrPoints = this->points.size();
Scan::allScans.clear();
}
model::Scene::Scene(const Scene& other) {
this->points = other.points;
this->planes = other.planes;
this->walls = other.walls;
this->ceiling = other.ceiling;
this->floor = other.floor;
}
model::Scene::~Scene() {
// delete the octree
if (this->octTree != NULL) {
delete this->octTree;
}
if (this->octTreePoints != NULL) {
for (unsigned int i = 0; i < this->nrPoints; ++i) {
if (this->octTreePoints[i] != NULL) {
delete[] this->octTreePoints[i];
}
}
delete[] this->octTreePoints;
}
}
vector<model::Plane3d> model::Scene::getConvexHull(vector<Plane3d> planes) {
if (planes.size() <= 3) {
return planes;
}
vector<Plane3d> hull;
vector<Plane3d>::iterator leftmostIt = planes.begin();
// compute the lefmost point
for (vector<Plane3d>::iterator it = planes.begin() + 1;
it != planes.end(); ++it)
{
// add only vertical planes
if (it->isVertical() && it->pt.x < leftmostIt->pt.x) {
leftmostIt = it;
}
}
Plane3d planeOnHull = *leftmostIt;
Plane3d endPlane;
do {
// add the candidate plane to the hull
hull.push_back(planeOnHull);
endPlane = planes[0];
for (unsigned int j = 1; j < planes.size(); ++j) {
// consider only vertical planes
if (planes[j].isVertical() == false) {
continue;
}
// define dummy planes in the XOZ plane
Point3d fakeCurPt(planes[j].pt.x, 0.0, planes[j].pt.z);
Point3d fakePointOnHull(planeOnHull.pt.x, 0.0, planeOnHull.pt.z);
Point3d fakeEndPoint(endPlane.pt.x, 0.0, endPlane.pt.z);
// define a point to the left of the line
Point3d farLeft = fakeEndPoint;
farLeft.rotate(planeOnHull.pt, Rotation3d(0.0, -M_PI/6, 0.0));
// if ... or planes[j] on the left side of line (planeOnHull, endPlane)
if (planeOnHull.pt != planes[j].pt &&
(endPlane.pt == planeOnHull.pt || sameSide(fakeCurPt, farLeft, fakePointOnHull, fakeEndPoint)))
{
endPlane = planes[j];
}
}
planeOnHull = endPlane;
} while (endPlane.pt != hull[0].pt);
return hull;
}
vector<model::Plane3d> model::Scene::getSignificantPlanes(vector<Plane3d> planes) {
vector<model::Plane3d> result;
while (!planes.empty()) {
// add the first plane as viable
result.push_back(planes.front());
planes.erase(planes.begin());;
for (vector<Plane3d>::iterator it = planes.begin(); it < planes.end(); ++it) {
if (result.back().isSamePlane(*it)) {
result.back().normal += it->normal;
planes.erase(it);
}
}
// normalize this normal after we have normalized
result.back().normal.normalize();
}
return result;
}
bool model::Scene::getCeiling(vector<Plane3d> planes, Plane3d& result) {
vector<Plane3d> horizontalPlanes;
for (vector<Plane3d>::iterator it = planes.begin(); it != planes.end(); ++it) {
if (it->isHorizontal()) {
horizontalPlanes.push_back(*it);
}
}
if (horizontalPlanes.size() <= 0) {
return false;
}
// start with the first horizontal plane
result = planes.front();
for (vector<Plane3d>::iterator it = planes.begin()+1;
it != planes.end(); ++it)
{
if (it->pt.y > result.pt.y) {
result = *it;
}
}
return true;
}
bool model::Scene::getFloor(vector<Plane3d> planes, Plane3d& result) {
vector<Plane3d> horizontalPlanes;
for (vector<Plane3d>::iterator it = planes.begin();
it != planes.end(); ++it)
{
if (it->isHorizontal()) {
horizontalPlanes.push_back(*it);
}
}
if (horizontalPlanes.size() <= 0) {
return false;
}
// start with the first horizontal plane
result = planes.front();
for (vector<Plane3d>::iterator it = planes.begin()+1;
it != planes.end(); ++it)
{
if (it->pt.y < result.pt.y) {
result = *it;
}
}
return true;
}
void model::Scene::detectWalls() {
if (!quiet) cout << endl << "== Building model..." << endl;
// temporary wall storage
vector<Plane3d> tempWalls = this->planes;
// extract the convex hull of the room
tempWalls = GraphicsAlg::getConcaveHull(tempWalls);
// throw away redundant tempWalls
tempWalls = this->getSignificantPlanes(tempWalls);
// get other bounds, ceiling and floor
Plane3d tempCeiling, tempFloor;
if (!this->getCeiling(this->planes, tempCeiling) || !this->getFloor(this->planes, tempFloor)) {
throw runtime_error("error while determining ceiling or floor");
}
if (tempWalls.size() < 4) {
throw runtime_error("room has less than 4 walls");
}
if (!quiet) cout << "** " << tempWalls.size() << " walls have been detected" << endl;
// XXX planes in the _walls vector should be in order
vector< pair<Point3d, Point3d> > corners;
Point3d upperCorner, lowerCorner;
if(!quiet) cout << endl << "== Computing corners..." << endl;
for (vector<Plane3d>::iterator it = tempWalls.begin(); it != tempWalls.end(); ++it) {
// be careful at the last element
if (it < tempWalls.end()-1) {
upperCorner = it->intersect(*(it+1), tempCeiling);
lowerCorner = it->intersect(*(it+1), tempFloor);
}
else if (it == tempWalls.end()-1) {
upperCorner = it->intersect(tempWalls.front(), tempCeiling);
lowerCorner = it->intersect(tempWalls.front(), tempFloor);
}
if (!quiet) cout << showpos << "** Computed upper corner " << upperCorner << endl;
if (!quiet) cout << showpos << "** Computed lower corner " << lowerCorner << endl;
corners.push_back(make_pair(upperCorner, lowerCorner));
}
if (!quiet) cout << endl << "== Building walls, ceiling and floor..." << endl;
// used to create the walls
Point3d upperLeft, upperRight;
Point3d lowerLeft, lowerRight;
// used to create the floor and the ceiling
vector<Point3d> ceilingHull;
vector<Point3d> floorHull;
Point3d ceilingCenter(0.0, 0.0, 0.0);
Point3d floorCenter(0.0, 0.0, 0.0);
Vector3d ceilingNormal;
Vector3d floorNormal;
for (vector< pair<Point3d, Point3d> >::iterator it = corners.begin();
it != corners.end(); ++it)
{
upperLeft = it->first;
lowerLeft = it->second;
if (it < corners.end()-1) {
upperRight = (it+1)->first;
lowerRight = (it+1)->second;
}
else if (it == corners.end()-1) {
upperRight = corners.front().first;
lowerRight = corners.front().second;
}
Point3d center((upperLeft.x + lowerLeft.x + upperRight.x + lowerRight.x) / 4.0,
(upperLeft.y + lowerLeft.y + upperRight.y + lowerRight.y) / 4.0,
(upperLeft.z + lowerLeft.z + upperRight.z + lowerRight.z) / 4.0);
vector<Point3d> hull;
hull.push_back(upperLeft);
hull.push_back(lowerLeft);
hull.push_back(lowerRight);
hull.push_back(upperRight);
if (!quiet) cout << "** Adding wall centered at " << center << endl;
this->walls.push_back(LabeledPlane3d(center, Vector3d(), hull));
this->walls.back().normal = this->walls.back().computeAverageNormal();
// compute the floor and the ceiling
ceilingHull.push_back(it->first);
floorHull.push_back(it->second);
ceilingCenter.x += it->first.x;
ceilingCenter.y += it->first.y;
ceilingCenter.z += it->first.z;
floorCenter.x += it->second.x;
floorCenter.y += it->second.y;
floorCenter.z += it->second.z;
}
ceilingCenter /= ceilingHull.size();
floorCenter /= floorHull.size();
// dummy normal
Vector3d dummy;
if (!quiet) cout << "** Adding ceiling centered at " << ceilingCenter << endl;
this->ceiling = LabeledPlane3d(ceilingCenter, dummy, ceilingHull);
this->ceiling.normal = this->ceiling.computeAverageNormal();
if (!quiet) cout << "** Adding floor centered at " << floorCenter << endl;
this->floor = LabeledPlane3d(floorCenter, dummy, floorHull);
this->floor.normal = this->floor.computeAverageNormal();
}
bool model::Scene::castRay(const Point3d& src, const Point3d& dest, const double& extraDist,
Point3d& ptHit)
{
// put the Bresenham line in here, we need some extra distance in case the points are close
// but after the detected wall, therefore we need to go a bit further than the wall to check
vector<Point3d> line;
GraphicsAlg::getDiscreteLine(src, dest, PRECISION, extraDist, line);
// loop through all the points in the Bresenham line and decide if it hits something
for (vector<Point3d>::iterator it = line.begin(); it != line.end(); ++it) {
if (this->isOccupied(*it, RAY_DIST)) {
ptHit = *it;
return true;
}
}
// no occlusion took place
return false;
}
void model::Scene::applyLabels(LabeledPlane3d& surf) {
if (this->poses.empty()) {
throw runtime_error("no poses have been provided to the scene");
}
// the maximum distance from which we subtract to create a depth image
double maxDist = 2.0 * WALL_DIST + RAY_DIST;
// determine the points on the plane for which to apply labels
vector<vector<Point3d> > discretePoints = surf.getDiscretePoints(PATCH_DIST);
surf.patches.resize(discretePoints.size());
surf.depthMap.resize(discretePoints.size());
// we need these values for later
surf.depthMapDistances.first = WALL_DIST;
surf.depthMapDistances.second = maxDist;
for (unsigned int i = 0; i < discretePoints.size(); ++i) {
for (unsigned int j = 0; j < discretePoints.front().size(); ++j) {
// consider everything is occluded initially
surf.patches[i].push_back(make_pair(discretePoints[i][j], OCCLUDED));
surf.depthMap[i].push_back(WALL_DIST);
}
}
if (!quiet) cout << endl << "== Performing ray casting for surface centered at " << surf.pt << endl;
for (vector<Pose6d>::iterator srcPose = this->poses.begin(); srcPose != this->poses.end(); ++srcPose) {
for (unsigned int i = 0; i < surf.patches.size(); ++i) {
for (unsigned int j = 0; j < surf.patches[i].size(); ++j) {
// prepare two points for ray casting through wall
Point3d ptOnWall = surf.patches[i][j].first;
Point3d src(surf.normal.x + ptOnWall.x,
surf.normal.y + ptOnWall.y,
surf.normal.z + ptOnWall.z);
double len = ptOnWall.distance(src);
double temp = (len + WALL_DIST) / len;
src.x = ptOnWall.x + (src.x - ptOnWall.x) * temp;
src.y = ptOnWall.y + (src.y - ptOnWall.y) * temp;
src.z = ptOnWall.z + (src.z - ptOnWall.z) * temp;
// remember the point we hit when ray casting
Point3d ptHit;
if (insideHull(surf.patches[i][j].first, surf.hull) && castRay(src, ptOnWall, WALL_DIST, ptHit)) {
surf.patches[i][j].second = OCCUPIED;
surf.depthMap[i][j] = maxDist - src.distance(ptHit);
} else if (!castRay(srcPose->first, surf.patches[i][j].first, 0, ptHit)) {
surf.patches[i][j].second = EMPTY;
surf.depthMap[i][j] = 0.0;
}
}
}
}
// create the OpenCV depth image
int imgHeight = static_cast<int>(surf.depthMap.size());
int imgWidth = static_cast<int>(surf.depthMap.front().size());
// crate an OpenCV image and allocate memory for it
cv::Mat img(imgHeight, imgWidth, CV_8UC1);
// compute the maximum from the image
double max = numeric_limits<double>::min();
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
if (max < surf.depthMap[i][j]) {
max = surf.depthMap[i][j];
}
}
}
// write the current image to a file
cv::Mat labels(imgHeight, imgWidth, CV_8UC3);
// adjust the image
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
img.data[(i * img.cols) + j] = static_cast<char>(MAX_IMG_VAL * surf.depthMap[i][j] / max);
// put the color into the image according to the label
int curr = (i * 3 * img.cols) + 3 * j;
switch (surf.patches[i][j].second) {
case EMPTY:
labels.data[curr + 0] = 0;
labels.data[curr + 1] = 200;
labels.data[curr + 2] = 0;
break;
case OCCLUDED:
labels.data[curr + 0] = 200;
labels.data[curr + 1] = 0;
labels.data[curr + 2] = 0;
break;
case OCCUPIED:
labels.data[curr + 0] = 0;
labels.data[curr + 1] = 0;
labels.data[curr + 2] = 200;
break;
default:
throw runtime_error("invalid default branch taken");
break;
}
}
}
cv::imwrite("./img/labels.png", labels);
// copy the image
surf.depthImg = img.clone();
}
void model::Scene::applyAllLabels() {
if (!quiet) cout << endl << "== Performing ray casting for all surfaces..." << endl;
applyLabels(this->ceiling);
applyLabels(this->floor);
for (vector<LabeledPlane3d>::iterator it = this->walls.begin(); it != this->walls.end(); ++it) {
applyLabels(*it);
}
}
void model::Scene::detectPotentialOpenings(const LabeledPlane3d& surf,
std::vector<CandidateOpening>& openings)
{
if (!quiet) cout << endl << "== Determining openings for surface centered at " << surf.pt << endl;
// get the candidate openings for current plane
vector<CandidateOpening> candidates;
surf.computeOpeningCandidates(candidates);
// TODO train the SVM using some outside variables, not hard coded in here, play around a lot with these numbers
// prepare to train the SVM
const unsigned int nrSamples = 68;
const unsigned int nrMainClass = 14;
const unsigned int nrFeatures = model::CandidateOpening::NR_FEATURES;
// make a set of excluded features
int excludedFeaturesInit[] = {6, 7, 8, 12, 13};
set<int> excludedFeatures(excludedFeaturesInit, excludedFeaturesInit + 5);
// set up the training data using some real life examples, needs to be flaot
float trainingData[nrSamples][14] = {
// TODO fix the RMS fit residual
// 1 area | 2 w/h | 3 w/W | 4 h/H | 5-8 dist to edges | 9 RMS | 10-12 E Occup Occl | 13 int rect | 14 int U-shapes
{ 3120, 0.42, 0.05, 0.36, 23, 138, 550, 126, 0, 0.93, 0.01, 0.06, 15, 0},
{ 3090, 0.42, 0.05, 0.36, 23, 138, 126, 550, 0, 0.93, 0.01, 0.06, 15, 0},
{ 6810, 0.94, 0.11, 0.34, 23, 140, 453, 176, 0, 0.91, 0.02, 0.07, 15, 0},
{ 6815, 0.94, 0.11, 0.34, 23, 140, 176, 453, 0, 0.91, 0.02, 0.07, 15, 0},
{11780, 1.52, 0.19, 0.35, 23, 139, 450, 128, 0, 0.89, 0.01, 0.10, 15, 0},
{11190, 1.52, 0.18, 0.35, 23, 139, 128, 450, 0, 0.88, 0.02, 0.10, 15, 0},
{10374, 1.70, 0.18, 0.31, 31, 141, 453, 124, 0, 0.87, 0.02, 0.11, 15, 0},
{10374, 1.70, 0.18, 0.31, 31, 141, 124, 453, 0, 0.87, 0.02, 0.11, 15, 0},
{27600, 0.82, 0.22, 0.62, 18, 97, 403, 148, 0, 0.95, 0.03, 0.02, 12, 0},
{27600, 0.82, 0.22, 0.62, 18, 97, 148, 403, 0, 0.94, 0.03, 0.03, 12, 0},
{25516, 0.87, 0.22, 0.59, 28, 100, 49, 396, 0, 0.84, 0.11, 0.05, 20, 0},
{26316, 0.87, 0.22, 0.59, 28, 100, 396, 49, 0, 0.84, 0.13, 0.03, 22, 0},
{25516, 0.87, 0.22, 0.59, 28, 100, 70, 396, 0, 0.84, 0.11, 0.05, 20, 0},
{26316, 0.87, 0.22, 0.59, 28, 100, 396, 70, 0, 0.84, 0.13, 0.03, 22, 0},
//========================================================================
{+143289, +2.10345, +0.786533, +0.887755, +31, +1, +1, +147, +0, +0.371222, +0.524681, +0.104097, +34300, +0},
{+180612, +2.65134, +0.991404, +0.887755, +31, +1, +1, +4, +0, +0.294571, +0.620845, +0.0845846, +44100, +0},
{+180873, +2.65517, +0.992837, +0.887755, +31, +1, +1, +3, +0, +0.294146, +0.621392, +0.0844626, +55125, +0},
{+181395, +2.66284, +0.995702, +0.887755, +31, +1, +1, +1, +0, +0.293299, +0.622481, +0.0842195, +67375, +0},
{+181656, +2.66667, +0.997135, +0.887755, +31, +1, +1, +0, +0, +0.292878, +0.621846, +0.0852766, +80850, +0},
{+181828, +2.64885, +0.994269, +0.891156, +31, +0, +0, +3, +0, +0.292634, +0.621114, +0.0862518, +70125, +2750},
{+143838, +2.09542, +0.786533, +0.891156, +31, +0, +1, +147, +0, +0.369805, +0.525466, +0.104729, +35700, +1400},
{+181304, +2.64122, +0.991404, +0.891156, +31, +0, +1, +4, +0, +0.293446, +0.621465, +0.0850891, +45900, +1800},
{+181566, +2.64504, +0.992837, +0.891156, +31, +0, +1, +3, +0, +0.293023, +0.622011, +0.0849663, +57375, +2250},
{+182090, +2.65267, +0.995702, +0.891156, +31, +0, +1, +1, +0, +0.29218, +0.623093, +0.0847273, +70125, +2750},
{+182352, +2.65649, +0.997135, +0.891156, +31, +0, +1, +0, +0, +0.29176, +0.622455, +0.0857846, +84150, +3300},
{+120270, +2.70142, +0.792768, +0.717687, +31, +51, +1, +147, +0, +0.427181, +0.486813, +0.0860065, +8400, +0},
{+150654, +3.38389, +0.993046, +0.717687, +31, +51, +1, +3, +0, +0.341299, +0.588308, +0.0703931, +10800, +0},
{+150865, +3.38863, +0.994437, +0.717687, +31, +51, +1, +2, +0, +0.340821, +0.588884, +0.0702946, +13500, +0},
{+39680, +0.605469, +0.222063, +0.870748, +32, +5, +396, +146, +0, +0.654738, +0.294254, +0.0510081, +3243, +0},
{+38656, +0.589844, +0.216332, +0.870748, +32, +5, +400, +146, +0, +0.672082, +0.27931, +0.0486082, +1081, +0},
{+39835, +0.603113, +0.222063, +0.87415, +32, +4, +396, +146, +0, +0.655153, +0.293787, +0.0510606, +3384, +0},
{+38807, +0.587549, +0.216332, +0.87415, +32, +4, +400, +146, +0, +0.672508, +0.278893, +0.0485995, +1128, +0},
{+39990, +0.600775, +0.222063, +0.877551, +32, +3, +396, +146, +0, +0.655539, +0.293373, +0.0510878, +3528, +0},
{+38958, +0.585271, +0.216332, +0.877551, +32, +3, +400, +146, +0, +0.672904, +0.278505, +0.0485908, +1176, +0},
{+40145, +0.598456, +0.222063, +0.880952, +32, +2, +396, +146, +0, +0.655374, +0.293586, +0.05104, +3675, +0},
{+39109, +0.583012, +0.216332, +0.880952, +32, +2, +400, +146, +0, +0.672735, +0.278759, +0.0485055, +1225, +0},
{+40300, +0.596154, +0.222063, +0.884354, +32, +1, +396, +146, +0, +0.652854, +0.296154, +0.0509926, +3825, +0},
{+39260, +0.580769, +0.216332, +0.884354, +32, +1, +400, +146, +0, +0.670148, +0.281457, +0.0483953, +1275, +0},
{+40455, +0.59387, +0.222063, +0.887755, +32, +0, +396, +146, +0, +0.650352, +0.298702, +0.0509455, +3978, +153},
{+39411, +0.578544, +0.216332, +0.887755, +32, +0, +400, +146, +0, +0.66758, +0.284134, +0.048286, +1326, +51},
{+115564, +4.14371, +0.991404, +0.568027, +33, +93, +1, +4, +0, +0.429745, +0.554031, +0.0162248, +990, +0},
{+115731, +4.1497, +0.992837, +0.568027, +33, +93, +1, +3, +0, +0.429124, +0.554674, +0.0162014, +1170, +0},
{+116065, +4.16168, +0.995702, +0.568027, +33, +93, +1, +1, +0, +0.42789, +0.555956, +0.0161547, +1365, +0},
{+116232, +4.16766, +0.997135, +0.568027, +33, +93, +1, +0, +0, +0.427275, +0.555544, +0.0171812, +1575, +0},
{+118674, +4.05848, +0.994269, +0.581633, +33, +89, +0, +3, +0, +0.419292, +0.556575, +0.0241333, +1911, +0},
{+119016, +4.07018, +0.997135, +0.581633, +33, +89, +0, +1, +0, +0.418087, +0.557849, +0.024064, +2205, +0},
{+118332, +4.04678, +0.991404, +0.581633, +33, +89, +1, +4, +0, +0.420453, +0.556739, +0.0228087, +1386, +0},
{+33790, +0.711009, +0.222063, +0.741497, +33, +42, +396, +146, +0, +0.736372, +0.215715, +0.0479136, +828, +0},
{+33136, +0.697248, +0.217765, +0.741497, +33, +42, +399, +146, +0, +0.750905, +0.201231, +0.0478634, +276, +0},
{+65880, +4.575, +0.786533, +0.408163, +33, +140, +1, +147, +0, +0.541166, +0.455055, +0.0037796, +45, +0},
{+66360, +4.60833, +0.792264, +0.408163, +33, +140, +1, +143, +0, +0.537251, +0.458996, +0.00375226, +55, +0},
{+135218, +3.59278, +0.998567, +0.659864, +33, +66, +0, +0, +0, +0.375216, +0.557995, +0.0667884, +11550, +0},
{+135135, +3.55385, +0.992837, +0.663265, +33, +65, +0, +4, +0, +0.375484, +0.557457, +0.0670589, +6468, +0},
{+135330, +3.55897, +0.994269, +0.663265, +33, +65, +0, +3, +0, +0.374943, +0.558095, +0.0669622, +8316, +0},
{+135720, +3.56923, +0.997135, +0.663265, +33, +65, +0, +1, +0, +0.373865, +0.559365, +0.0667698, +10395, +0},
{+135915, +3.57436, +0.998567, +0.663265, +33, +65, +0, +0, +0, +0.373329, +0.558893, +0.0677777, +12705, +0},
{+142065, +3.38049, +0.992837, +0.697279, +33, +55, +0, +4, +0, +0.357998, +0.572006, +0.0699961, +7728, +0},
{+117117, +4.10059, +0.992837, +0.57483, +30, +94, +0, +4, +0, +0.428213, +0.555837, +0.0159499, +280, +0},
{+117286, +4.10651, +0.994269, +0.57483, +30, +94, +0, +3, +0, +0.427596, +0.556477, +0.0159269, +360, +0},
{+117624, +4.11834, +0.997135, +0.57483, +30, +94, +0, +1, +0, +0.426367, +0.557752, +0.0158811, +450, +0},
{+117793, +4.12426, +0.998567, +0.57483, +30, +94, +0, +0, +0, +0.425755, +0.557359, +0.0168856, +550, +0},
{+43176, +1.52976, +0.368195, +0.571429, +30, +95, +396, +44, +0, +0.571822, +0.428178, +0, +36, +0},
{+43344, +1.53571, +0.369628, +0.571429, +30, +95, +396, +43, +0, +0.569606, +0.430394, +0, +60, +0},
{+42672, +1.5119, +0.363897, +0.571429, +30, +95, +399, +44, +0, +0.578576, +0.421424, +0, +18, +0},
{+42840, +1.51786, +0.36533, +0.571429, +30, +95, +399, +43, +0, +0.576307, +0.423693, +0, +36, +0},
{+42504, +1.50595, +0.362464, +0.571429, +30, +95, +400, +44, +0, +0.580863, +0.419137, +0, +6, +0},
{+42672, +1.5119, +0.363897, +0.571429, +30, +95, +400, +43, +0, +0.578576, +0.421424, +0, +18, +0},
{+33338, +0.748815, +0.226361, +0.717687, +29, +53, +396, +143, +0, +0.760244, +0.195453, +0.0443038, +1134, +0},
};
// match the labels to the training data
float mainLabel = 1.0;
float labels[nrSamples];
// create the labels
for (unsigned int i = 0; i < nrSamples; ++i) {
if (i < nrMainClass) {
labels[i] = mainLabel;
} else {
labels[i] = -mainLabel;
}
}
// normalize the values above
float maxFeatures[nrFeatures];
for (unsigned int i = 0; i < nrFeatures; ++i) {
maxFeatures[i] = numeric_limits<float>::min();
}
for (unsigned int i = 0; i < nrSamples; ++i) {
for (unsigned int j = 0; j < nrFeatures; ++j) {
if (maxFeatures[j] < trainingData[i][j]) {
maxFeatures[j] = trainingData[i][j];
}
}
}
for (unsigned int i = 0; i < nrSamples; ++i) {
for (unsigned int j = 0; j < nrFeatures; ++j) {
if (excludedFeatures.find(j) != excludedFeatures.end()) {
trainingData[i][j] = 0.0;
} else {
trainingData[i][j] /= maxFeatures[j];
}
}
}
// compute the max of the values of the candidates, but normalize later
for (unsigned int i = 0; i < nrFeatures; ++i) {
maxFeatures[i] = numeric_limits<float>::min();
}
for (vector<CandidateOpening>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
for (unsigned int feat = 0; feat < nrFeatures; ++feat) {
if (maxFeatures[feat] < it->features[feat]) {
maxFeatures[feat] = it->features[feat];
}
}
}
// create two OpenCV matrices
cv::Mat trainingMat(nrSamples, nrFeatures, CV_32FC1, trainingData);
cv::Mat labelsMat(nrSamples, 1, CV_32FC1, labels);
// set up the SVM parameters
cv::SVMParams params;
params.svm_type = cv::SVM::NU_SVC;
params.kernel_type = cv::SVM::RBF;
params.term_crit = cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 1000, 1e-6);
params.nu = 0.8;
params.C = 8.0;
params.gamma = 20;
// create SVM and train it
cv::SVM svm;
svm.train_auto(trainingMat, labelsMat, cv::Mat(), cv::Mat(), params); // XXX with or without auto
// store these openings, and filter some more later on
std::vector<CandidateOpening> tempOpenings;
for (vector<CandidateOpening>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
if (it->features.size() != nrFeatures) {
throw runtime_error("number of features in candidate in does not equal the expected one");
}
float candidateData[nrFeatures];
for (size_t jt = 0; jt < it->features.size(); ++jt) {
if (excludedFeatures.find(jt) != excludedFeatures.end()) {
candidateData[jt] = 0.0;
} else {
// also normalize the data here, and not in the candidateOpening object
candidateData[jt] = static_cast<float>(it->features[jt]) / maxFeatures[jt];
}
}
cv::Mat candidateMat(1, nrFeatures, CV_32FC1, candidateData);
// decide of which type the current opening is
float response = svm.predict(candidateMat);
if (response == mainLabel) {
tempOpenings.push_back(*it);
}
}
// filter openings with respect to how much they overlap with the Canny edge
cv::Mat canny, hSobel, vSobel, combined;
surf.detectEdges(canny, hSobel, vSobel, combined);
double maxEdgeCoverage = numeric_limits<double>::min();
double averageArea = 0.0;
for (vector<CandidateOpening>::iterator it = tempOpenings.begin(); it != tempOpenings.end(); ++it) {
int y1 = it->edges[0];
int y2 = it->edges[1];
int x1 = it->edges[2];
int x2 = it->edges[3];
// how many pixels overlap?
int total = 2 * (y2 - y1 + x2 - x1) + 1;
int count = total;
if (y2 < y1 || x2 < x1) {
throw runtime_error("candidate edges must be in proper order");
}
for (int i = y1; i <= y2; ++i) {
if (combined.data[i * combined.cols + x1] == 0) {
count--;
}
if (combined.data[i * combined.cols + x2] == 0) {
count--;
}
}
for (int i = x1; i <= x2; ++i) {
if (combined.data[y1 * combined.cols + i] == 0) {
count--;
}
if (combined.data[y2 * combined.cols + i] == 0) {
count--;
}
}
// remember the edge coverage inside the object
it->edgeCoverage = static_cast<double>(count) / total;
if (maxEdgeCoverage < it->edgeCoverage) {
maxEdgeCoverage = it->edgeCoverage;
}
// increment the average area
averageArea += it->features[0];
}
averageArea /= tempOpenings.size();
unsigned int discarded = 0;
for (vector<CandidateOpening>::iterator it = tempOpenings.begin(); it != tempOpenings.end(); ++it) {
// TODO put coef in class
if ((it->features[0] > 0.3 * averageArea) &&
(it->edgeCoverage > MIN_EDGE_COV * maxEdgeCoverage))
{
openings.push_back(*it);
} else {
discarded++;
}
}
if (!quiet) cout << "** Discarded openings due to relative size to average candidate: " << discarded << endl;
// draw the chosen rectangles, and also the occupancy map
cv::Mat imgWithOpenings = surf.depthImg;
cv::cvtColor(imgWithOpenings, imgWithOpenings, CV_GRAY2BGR, 3);
for (size_t i = 0; i < openings.size(); ++i) {
int y1 = openings[i].edges[0];
int y2 = openings[i].edges[1];
int x1 = openings[i].edges[2];
int x2 = openings[i].edges[3];
cv::rectangle(imgWithOpenings, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(0, 0, MAX_IMG_VAL), 1);
}
cv::imwrite("./img/depthCandidates.png", imgWithOpenings);
if (!quiet) cout << "** Detected " << openings.size() << " potential openings" << endl;
}
void model::Scene::clusterOpenings(const LabeledPlane3d& surf, const vector<CandidateOpening>& openings,
std::vector<CandidateOpening>& result) const
{
// clear the result just in case
result.clear();
if (openings.size() == 1) {
if (!quiet) cerr << "WARNING: only one candidate received for clustering!" << endl;
result.push_back(openings.front());
return;
} else if (openings.size() < 1) {
if (!quiet) cerr << "WARNING: no candidates received for clustering!" << endl;
return;
}
if (!quiet) cout << endl << "== Clustering openings for surface centered at " << surf.pt << endl;
size_t nrSamples = openings.size();
unsigned int dim = 7;
// a few matrix initializations
CvMat* points = cvCreateMat(nrSamples, dim, CV_32FC1);
CvMat* clusters = cvCreateMat(nrSamples, 1, CV_32SC1);
// fill in the matrices with data
for (size_t i = 0; i < nrSamples; ++i) {
if (openings[i].features.size() != model::CandidateOpening::NR_FEATURES) {
throw runtime_error("invalid feature vector size");
}
// compute the center of each opening
float x = static_cast<float>(openings[i].edges[2] + openings[i].edges[3]) / 2.0;
float y = static_cast<float>(openings[i].edges[0] + openings[i].edges[1]) / 2.0;
points->data.fl[i * dim + 0] = x; // window center
points->data.fl[i * dim + 1] = y; // window center
points->data.fl[i * dim + 2] = openings[i].edges[0]; // upper edge
points->data.fl[i * dim + 3] = openings[i].edges[1]; // lower edge
points->data.fl[i * dim + 4] = openings[i].edges[2]; // left edge
points->data.fl[i * dim + 5] = openings[i].edges[3]; // right edge
points->data.fl[i * dim + 6] = openings[i].features[1];// edge ratio
}
// termination criteria for the kMeans algorithm
CvTermCriteria term;
term.type = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS;
term.max_iter = 10;
term.epsilon = 1.0;
int maxNrClusters = 256;
int nrClusters = 2; // from how many clusters to start
double prev, compactness; // previous compactness, and current compactness
CvMat *centers = cvCreateMat(nrClusters - 1, dim, CV_32FC1); // place where we compute the centers of each cluster
cvKMeans2(points, nrClusters - 1, clusters, term, 1, 0, 0, centers, &prev);
while (1) {
cvReleaseMat(&centers);
centers = cvCreateMat(nrClusters, dim, CV_32FC1);
cvKMeans2(points, nrClusters, clusters, term, 1, 0, 0, centers, &compactness);
// TODO put somewhere everything in class
double deltaCompactness = compactness - prev;
if (!quiet) cout << "** Delta compactness: " << deltaCompactness << endl;
if (fabs(deltaCompactness) < 1.0 || nrClusters >= maxNrClusters) {
break;
}
prev = compactness;
nrClusters++;
}
if (!quiet) cout << "** Detected " << static_cast<unsigned int>(nrClusters) << " clusters of potential openings" << endl;
// generate colors for each cluster
int r, g, b;
cv::Scalar colors[nrClusters];
for (int i = 0; i < nrClusters; ++i) {
randomColor(MAX_IMG_VAL, r, g, b);
colors[i] = (cv::Scalar(b, g, r));
}
// create an image with the clusters in separate colors
cv::Mat clusterImg = surf.depthImg;
cv::Mat finalImg = surf.depthImg;
cv::cvtColor(clusterImg, clusterImg, CV_GRAY2BGR, 3);
cv::cvtColor(finalImg, finalImg, CV_GRAY2BGR, 3);
// count how many elements we have in each cluster
vector<int> clusterCount;
clusterCount.resize(nrClusters);
fill(clusterCount.begin(), clusterCount.end(), 0);
// compute the candidate for each cluster with the best coverage of empty area
vector<CandidateOpening> bestCandidates;
bestCandidates.resize(nrClusters);
vector<double> bestEmptyAreas;
bestEmptyAreas.resize(nrClusters);
fill(bestEmptyAreas.begin(), bestEmptyAreas.end(), numeric_limits<double>::min());
// fill in the result separating each cluster
for (size_t i = 0; i < nrSamples; ++i) {
// the cluster of the current opening
int currCluster = clusters->data.i[i];
// draw each cluster
int y1 = openings[i].edges[0];
int y2 = openings[i].edges[1];
int x1 = openings[i].edges[2];
int x2 = openings[i].edges[3];
cv::rectangle(clusterImg, cv::Point(x1, y1), cv::Point(x2, y2), colors[currCluster], 1);
// TODO avoid hard coding
// find which is the best choice from the cluster
double currEmptyArea = openings[i].features[0] *
(openings[i].features[9] - openings[i].features[10] - openings[i].features[11]);
if (bestEmptyAreas[currCluster] < currEmptyArea) {
bestEmptyAreas[currCluster] = currEmptyArea;
bestCandidates[currCluster] = openings[i];
}
clusterCount[currCluster]++;
}
// divide by the cluster count to get the average of the cluster
for (size_t i = 0; i < bestEmptyAreas.size(); ++i) {
if (clusterCount[i] <= 0) {
continue;
}
int y1 = bestCandidates[i].edges[0];
int y2 = bestCandidates[i].edges[1];
int x1 = bestCandidates[i].edges[2];
int x2 = bestCandidates[i].edges[3];
cv::rectangle(finalImg, cv::Point(x1, y1), cv::Point(x2, y2), colors[i], 2);
result.push_back(bestCandidates[i]);
}
if (!quiet) {
cout << "** The count for each cluster is [";
for (unsigned int i = 0; i < clusterCount.size() - 1; ++i) {
cout << clusterCount[i] << ", ";
}
cout << clusterCount.back() << "]" << endl;
}
// display the center of the cluster only for the clusters that have candidates
for (int i = 0; i < nrClusters; ++i) {
if (clusterCount[i] > 0) {
int x = static_cast<int>(centers->data.fl[i * dim + 0]);
int y = static_cast<int>(centers->data.fl[i * dim + 1]);
cv::circle(clusterImg, cv::Point(x, y), 2, colors[i], CV_FILLED);
cv::circle(finalImg, cv::Point(x, y), 2, colors[i], CV_FILLED);
}
}
cv::imwrite("./img/depthClusters.png", clusterImg);
cv::imwrite("./img/depthOpenings.png", finalImg);
cvReleaseMat(&points);
cvReleaseMat(&centers);
cvReleaseMat(&clusters);
}
void model::Scene::addFinalOpenings(const LabeledPlane3d& surf,
std::vector<CandidateOpening>& result)
{
int imgHeight = static_cast<int>(surf.depthMap.size());
int imgWidth = static_cast<int>(surf.depthMap.front().size());
vector<CandidateOpening> openings, candidates;
this->detectPotentialOpenings(surf, openings);
this->clusterOpenings(surf, openings, candidates);
// sweep across the wall and see which candidates overlap
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
// put currently overlapping candidates here
vector<pair<vector<CandidateOpening>::iterator, double> > temp;
// the maximum opening area in the current overlapping windows
double bestEmptyArea = numeric_limits<double>::min();
for (vector<CandidateOpening>::iterator it = candidates.begin(); it < candidates.end(); ++it) {
int y1 = it->edges[0];
int y2 = it->edges[1];
int x1 = it->edges[2];
int x2 = it->edges[3];
if (i >= y1 && i < y2 && j >= x1 && j <= x2) {
// TODO avoid hard coding
double freeArea = it->features[0] *
(it->features[9] - it->features[10] - it->features[11]);
temp.push_back(make_pair(it, freeArea));
if (bestEmptyArea < freeArea) {
bestEmptyArea = freeArea;
}
}
}
for (vector<pair<vector<CandidateOpening>::iterator, double> >::iterator it = temp.begin(); it < temp.end(); ++it) {
// if the area is smaller than the maximum discard this plane
if (it->second < bestEmptyArea) {
candidates.erase(it->first);
}
}
}
}
// display an image with the final openings
int r, g, b;
cv::Mat finalOpeningsImg = surf.depthImg.clone();
cv::cvtColor(finalOpeningsImg, finalOpeningsImg, CV_GRAY2BGR, 3);
for (vector<CandidateOpening>::iterator it = candidates.begin(); it < candidates.end(); ++it) {
int y1 = it->edges[0];
int y2 = it->edges[1];
int x1 = it->edges[2];
int x2 = it->edges[3];
randomColor(MAX_IMG_VAL, r, g, b);
cv::rectangle(finalOpeningsImg, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(b, g, r), 3);
// add the new openings as windows
Point3d pt(0.0, 0.0, 0.0);
pt += surf.patches[y1].front().first;
pt += surf.patches[y2].front().first;
pt += surf.patches.front()[x1].first;
pt += surf.patches.front()[x2].first;
pt /= 4.0;
vector<Point3d> hull;
hull.push_back(surf.patches[y1][x1].first);
hull.push_back(surf.patches[y1][x2].first);
hull.push_back(surf.patches[y2][x2].first);
hull.push_back(surf.patches[y2][x1].first);
Plane3d toPush(pt, surf.normal, hull);
this->finalOpenings.push_back(toPush);
}
cv::imwrite("./img/depthOpeningsFinal.png", finalOpeningsImg);
if (!quiet) cout << "** Final openings found on current surface: " << candidates.size() << endl;
if (!quiet) cout << "** Final openings found so far: " << this->finalOpenings.size() << endl;
// copy the result
result = candidates;
}
void model::Scene::correct(LabeledPlane3d& surf, const vector<CandidateOpening>& openings) {
if (!quiet) cout << endl << "== Correcting surface centered at " << surf.pt << endl;
int imgHeight = static_cast<int>(surf.depthMap.size());
int imgWidth = static_cast<int>(surf.depthMap.front().size());
// some images we are interested in
cv::Mat depthImg = surf.depthImg.clone();
cv::Mat depthImgMask(imgHeight, imgWidth, CV_8UC1);
cv::Mat labelsImg(imgHeight, imgWidth, CV_8UC3);
// compute the maximum from the image
double max = numeric_limits<double>::min();
double mean = 0.0, stdDev = 0.0;
int count = 0;
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
// set all empty and occluded to unknown
if (surf.patches[i][j].second == EMPTY || surf.patches[i][j].second == OCCLUDED) {
surf.patches[i][j].second = UNKOWN;
// compute the mean of the occupied patches
} else if (surf.patches[i][j].second == OCCUPIED) {
mean += surf.depthImg.data[i * surf.depthImg.cols + j];
count++;
}
// compute the maximum from the image
if (max < surf.depthMap[i][j]) {
max = surf.depthMap[i][j];
}
}
}
// adjust the mean
mean /= count;
count = 0;
// compute the standard deviation
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
if (surf.patches[i][j].second == OCCUPIED) {
stdDev += pow(surf.depthImg.data[i * surf.depthImg.cols + j] - mean, 2.0);
count++;
}
}
}
// adjust the standard deviation
stdDev = sqrt(stdDev / count);
// mark all patches contained in openings
for (vector<CandidateOpening>::const_iterator it = openings.begin(); it != openings.end(); ++it) {
int y1 = it->edges[0];
int y2 = it->edges[1];
int x1 = it->edges[2];
int x2 = it->edges[3];
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
if (i >= y1 && i <= y2 && j >= x1 && j <= x2) {
surf.patches[i][j].second = OPENING;
}
}
}
}
// adjust the image
for (int i = 0; i < imgHeight; ++i) {
for (int j = 0; j < imgWidth; ++j) {
// initially mark as not part of the mask
depthImgMask.data[i * depthImgMask.cols + j] = 0;
// put the color into the image according to the label
int curr = (i * 3 * labelsImg.cols) + 3 * j;
switch (surf.patches[i][j].second) {
case OPENING:
labelsImg.data[curr + 0] = 200;
labelsImg.data[curr + 1] = 200;
labelsImg.data[curr + 2] = 200;
break;
case UNKOWN:
labelsImg.data[curr + 0] = 50;
labelsImg.data[curr + 1] = 50;
labelsImg.data[curr + 2] = 50;
// add pixel to mask for inpainting
depthImgMask.data[i * depthImgMask.cols + j] = 200;
break;
case OCCUPIED:
labelsImg.data[curr + 0] = 0;
labelsImg.data[curr + 1] = 0;
labelsImg.data[curr + 2] = 200;
break;
default:
throw runtime_error("invalid default branch taken, possibly EMPTY or OCCLUDED patches");
break;
}
}
}
cv::imwrite("./img/labelsWithOpenings.png", labelsImg);
cv::imwrite("./img/depthMask.png", depthImgMask);
cv::imwrite("./img/depthSimple.png", depthImg);
// TODO add values as part of class
if (!quiet) cout << "** Filling in missing data using inpaint algorithm" << endl;
cv::inpaint(depthImg, depthImgMask, depthImg, 0.05 * (imgWidth + imgHeight), cv::INPAINT_NS);
cv::imwrite("./img/depthInpaint.png", depthImg);
// add some gaussian noise to make the image look more real
if (!quiet) cout << "** Adding gaussian noise: mean = " << mean << ", stdDev = " << stdDev << endl;
cv::Mat noise(imgHeight, imgWidth, CV_8UC1);
cv::randn(noise, mean, stdDev);
cv::addWeighted(depthImg, 0.9, noise, 0.1, 0.0, depthImg);
cv::GaussianBlur(depthImg, depthImg, cv::Size(3, 3), 0.5, 0.5);
cv::imwrite("./img/depthInpaintNoise.png", depthImg);
surf.correctedDepthImg = depthImg.clone();
// TODO apply the inverse transform from 2D to 3D and create a new point cloud using UOS RGB
}
void model::Scene::writeModel(string dir) {
if (!fileIsDir(dir)) {
throw runtime_error(dir + " is not a directory");
}
string outputDir;
if (dir[dir.length()-1] == '/') {
outputDir = dir + "model";
}
else {
outputDir = dir + "/model";
}
int ret = mkdir(outputDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
if (ret != 0 && errno != EEXIST) {
throw runtime_error("unable to create directory " + outputDir);
}
if (!quiet) cout << endl << "== Writing planes to " << outputDir << " directory..." << endl;
string planesListFile = outputDir + "/planes.list";
ofstream list(planesListFile.c_str(), ios::out);
for (unsigned int counter = 0; counter < this->walls.size() + this->finalOpenings.size() + 2; ++counter) {
Plane3d currPlane; // the plane to be printed
string type; // the objet's type, floor, ceiling, wall or opening
if (counter < this->walls.size()) {
currPlane = this->walls[counter];
type = "Wall";
}
else if (counter == this->walls.size()) {
currPlane = this->ceiling;
type = "Ceiling";
}
else if (counter == this->walls.size() + 1) {
currPlane = this->floor;
type = "Floor";
} else {
// TODO find a better solution
currPlane = this->finalOpenings[counter - (this->walls.size() + 2)];
currPlane.normal.normalize();
for (vector<Point3d>::iterator it = currPlane.hull.begin(); it != currPlane.hull.end(); ++it) {
it->x += currPlane.normal.x;
it->y += currPlane.normal.y;
it->z += currPlane.normal.z;
}
type = "Opening";
}
stringstream ss;
ss << outputDir << "/scan" << setfill('0') << setw(3) << counter << ".3d";
ofstream file(ss.str().c_str(), ios::out);
// add the plane to the list of planes
list << type << " " << ss.str() << endl;
boost::algorithm::to_lower(type);
if (!quiet) cout << "** Writing " << type << ss.str() << endl;
// add the points to the scan file
for (vector<Point3d>::iterator it = currPlane.hull.begin();
it != currPlane.hull.end(); ++it)
{
file << *it << endl;
}
file.close();
}
list.close();
}
void model::Scene::writeCorrectedWalls(std::string dir) {
if (!fileIsDir(dir)) {
throw runtime_error(dir + " is not a directory");
}
string outputDir;
if (dir[dir.length()-1] == '/') {
outputDir = dir + "cloud";
}
else {
outputDir = dir + "/cloud";
}
int ret = mkdir(outputDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
if (ret != 0 && errno != EEXIST) {
throw runtime_error("unable to create directory " + outputDir);
}
if (!quiet) cout << endl << "== Writing corrected points to " << outputDir << " directory..." << endl;
// write the pose file
string poseFileName = outputDir + "/scan000.pose";
ofstream out(poseFileName.c_str(), ios::out);
out << "0 0 0" << endl;
out << "0 0 0" << endl;
out.close();
string pointsFileName = outputDir + "/scan000.3d";
out.open(pointsFileName.c_str(), ios::out);
// fetch each points and its color
vector<pair<Point3d, cv::Vec3i> > points;
getCorrectedWall(this->ceiling, points);
for (vector<pair<Point3d, cv::Vec3i> >::iterator it = points.begin(); it != points.end(); ++it) {
out << it->first << " " << static_cast<int>(it->second[0]) << " " << static_cast<int>(it->second[1]) << " " << static_cast<int>(it->second[2]) << endl;
}
getCorrectedWall(this->floor, points);
for (vector<pair<Point3d, cv::Vec3i> >::iterator it = points.begin(); it != points.end(); ++it) {
out << it->first << " " << static_cast<int>(it->second[0]) << " " << static_cast<int>(it->second[1]) << " " << static_cast<int>(it->second[2]) << endl;
}
for (vector<LabeledPlane3d>::iterator wall = this->walls.begin(); wall != this->walls.end(); ++wall) {
getCorrectedWall(*wall, points);
for (vector<pair<Point3d, cv::Vec3i> >::iterator it = points.begin(); it != points.end(); ++it) {
out << it->first << " " << static_cast<int>(it->second[0]) << " " << static_cast<int>(it->second[1]) << " " << static_cast<int>(it->second[2]) << endl;
}
}
out.close();
}
void model::Scene::getCorrectedWall(const LabeledPlane3d& surf, vector<pair<Point3d, cv::Vec3i> >& points) {
if (!quiet) cout << "** Computing point cloud for surface centered at " << surf.pt << endl;
// clear the output
points.clear();
if (surf.correctedDepthImg.empty()) {
if (!quiet) cout << "WARNING: the surface centered at " << surf.pt << " has not been corrected yet" << endl;
return;
}
if ((size_t) surf.correctedDepthImg.rows != surf.patches.size() ||
(size_t) surf.correctedDepthImg.cols != surf.patches.front().size())
{
throw runtime_error("corrected image must be the same size as depth map");
}
// compute the maximum of the depth image
int depthImgMax = 0;
for (size_t i = 0; i < surf.depthMap.size(); ++i) {
for (size_t j = 0; j < surf.depthMap.front().size(); ++j) {
if (depthImgMax < surf.depthMap[i][j]) {
depthImgMax = surf.depthMap[i][j];
}
}
}
// make the original points darker than the corrected ones
int r = 255, g = 50, b = 50;
int wr = 255, wg = 150, wb = 150;
for (int i = 0; i < surf.correctedDepthImg.rows; ++i) {
for (int j = 0; j < surf.correctedDepthImg.cols; ++j) {
cv::Vec3i color;
// only consider occupied and unknown pixels
if (surf.patches[i][j].second == OCCUPIED) {
color = cv::Vec3i(r, g, b);
} else if (surf.patches[i][j].second == UNKOWN) {
color = cv::Vec3i(wr, wg, wb);
} else {
continue;
}
// the value from the depth map
double depth = static_cast<double>(depthImgMax * surf.correctedDepthImg.data[i * surf.correctedDepthImg.cols + j]) / MAX_IMG_VAL;
// the distance from where the raytracing took place to the point it hit
double dist = depth + surf.depthMapDistances.first - surf.depthMapDistances.second;
Vector3d normal = surf.normal;
normal.normalize();
Point3d pt = surf.patches[i][j].first;
pt.x = pt.x + normal.x * dist;
pt.y = pt.y + normal.y * dist;
pt.z = pt.z + normal.z * dist;
points.push_back(make_pair(pt, color));
}
}
}
void model::Scene::flood(LabeledPlane3d& surf,
const int& i, const int& j,
const Label& target, const Label& replacement)
{
// no point of carrying on
if (target == replacement) {
return;
}
pair<int, int> north = make_pair(i-1, j);
pair<int, int> south = make_pair(i+1, j);
pair<int, int> east = make_pair(i, j+1);
pair<int, int> west = make_pair(i, j-1);
vector<pair<int, int> > neigh;
neigh.push_back(north);
neigh.push_back(south);
neigh.push_back(east);
neigh.push_back(west);
if (surf.patches[i][j].second == target) {
surf.patches[i][j].second = replacement;
}
for (vector<pair<int, int> >::iterator it = neigh.begin(); it != neigh.end(); ++it) {
if ((it->first > 0 && it->second > 0) &&
surf.patches[it->first][it->second].second == target)
{
flood(surf, it->first, it->second, target, replacement);
}
}
}
bool model::Scene::isOccupied(const Point3d& center, const double& width) {
// look for a close point
double pt[] = {center.x, center.y, center.z};
double* closest = this->octTree->FindClosest(pt, pow(sqrt(3) * (width / 2.0), 2.0), 0);
// bounding box limits
double xUppLim = center.x + width / 2.0;
double xLowLim = center.x - width / 2.0;
double yUppLim = center.y + width / 2.0;
double yLowLim = center.y - width / 2.0;
double zUppLim = center.z + width / 2.0;
double zLowLim = center.z - width / 2.0;
if (closest != NULL &&
(closest[0] > xLowLim && closest[0] < xUppLim) &&
(closest[1] > yLowLim && closest[1] < yUppLim) &&
(closest[2] > zLowLim && closest[2] < zUppLim))
{
return true;
}
return false;
}