1
0
mirror of https://github.com/opencv/opencv_contrib.git synced 2025-10-19 19:44:14 +08:00
Files
opencv_contrib/modules/surface_matching/src/ppf_helpers.cpp

819 lines
21 KiB
C++

//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2014, OpenCV Foundation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
// Author: Tolga Birdal <tbirdal AT gmail.com>
#include "precomp.hpp"
namespace cv
{
namespace ppf_match_3d
{
typedef cv::flann::L2<float> Distance_32F;
typedef cv::flann::GenericIndex< Distance_32F > FlannIndex;
void shuffle(int *array, size_t n);
Mat genRandomMat(int rows, int cols, double mean, double stddev, int type);
void getRandQuat(double q[4]);
void getRandomRotation(double R[9]);
void meanCovLocalPC(const float* pc, const int ws, const int point_count, double CovMat[3][3], double Mean[4]);
void meanCovLocalPCInd(const float* pc, const int* Indices, const int ws, const int point_count, double CovMat[3][3], double Mean[4]);
Mat loadPLYSimple(const char* fileName, int withNormals)
{
Mat cloud;
int numVertices=0;
std::ifstream ifs(fileName);
if (!ifs.is_open())
{
printf("Cannot open file...\n");
return Mat();
}
std::string str;
while (str.substr(0, 10) !="end_header")
{
std::string entry = str.substr(0, 14);
if (entry == "element vertex")
{
numVertices = atoi(str.substr(15, str.size()-15).c_str());
}
std::getline(ifs, str);
}
if (withNormals)
cloud=Mat(numVertices, 6, CV_32FC1);
else
cloud=Mat(numVertices, 3, CV_32FC1);
for (int i = 0; i < numVertices; i++)
{
float* data = (float*)(&cloud.data[i*cloud.step[0]]);
if (withNormals)
{
ifs >> data[0] >> data[1] >> data[2] >> data[3] >> data[4] >> data[5];
// normalize to unit norm
double norm = sqrt(data[3]*data[3] + data[4]*data[4] + data[5]*data[5]);
if (norm>0.00001)
{
data[3]/=(float)norm;
data[4]/=(float)norm;
data[5]/=(float)norm;
}
}
else
{
ifs >> data[0] >> data[1] >> data[2];
}
}
//cloud *= 5.0f;
return cloud;
}
void writePLY(Mat PC, const char* FileName)
{
std::ofstream outFile( FileName );
if ( !outFile )
{
//cerr << "Error opening output file: " << FileName << "!" << endl;
printf("Error opening output file: %s!\n", FileName);
exit( 1 );
}
////
// Header
////
const int pointNum = ( int ) PC.rows;
const int vertNum = ( int ) PC.cols;
outFile << "ply" << std::endl;
outFile << "format ascii 1.0" << std::endl;
outFile << "element vertex " << pointNum << std::endl;
outFile << "property float x" << std::endl;
outFile << "property float y" << std::endl;
outFile << "property float z" << std::endl;
if (vertNum==6)
{
outFile << "property float nx" << std::endl;
outFile << "property float ny" << std::endl;
outFile << "property float nz" << std::endl;
}
outFile << "end_header" << std::endl;
////
// Points
////
for ( int pi = 0; pi < pointNum; ++pi )
{
const float* point = (float*)(&PC.data[ pi*PC.step ]);
outFile << point[0] << " "<<point[1]<<" "<<point[2];
if (vertNum==6)
{
outFile<<" " << point[3] << " "<<point[4]<<" "<<point[5];
}
outFile << std::endl;
}
return;
}
void writePLYVisibleNormals(Mat PC, const char* FileName)
{
std::ofstream outFile(FileName);
if (!outFile)
{
//cerr << "Error opening output file: " << FileName << "!" << endl;
printf("Error opening output file: %s!\n", FileName);
exit(1);
}
////
// Header
////
const int pointNum = (int)PC.rows;
const int vertNum = (int)PC.cols;
const bool hasNormals = vertNum == 6;
outFile << "ply" << std::endl;
outFile << "format ascii 1.0" << std::endl;
outFile << "element vertex " << (hasNormals? 2*pointNum:pointNum) << std::endl;
outFile << "property float x" << std::endl;
outFile << "property float y" << std::endl;
outFile << "property float z" << std::endl;
if (hasNormals)
{
outFile << "property uchar red" << std::endl;
outFile << "property uchar green" << std::endl;
outFile << "property uchar blue" << std::endl;
}
outFile << "end_header" << std::endl;
////
// Points
////
for (int pi = 0; pi < pointNum; ++pi)
{
const float* point = (float*)(&PC.data[pi*PC.step]);
outFile << point[0] << " " << point[1] << " " << point[2];
if (hasNormals)
{
outFile << " 127 127 127" << std::endl;
outFile << point[0]+point[3] << " " << point[1]+point[4] << " " << point[2]+point[5];
outFile << " 255 0 0";
}
outFile << std::endl;
}
return;
}
Mat samplePCUniform(Mat PC, int sampleStep)
{
int numRows = PC.rows/sampleStep;
Mat sampledPC = Mat(numRows, PC.cols, PC.type());
int c=0;
for (int i=0; i<PC.rows && c<numRows; i+=sampleStep)
{
PC.row(i).copyTo(sampledPC.row(c++));
}
return sampledPC;
}
Mat samplePCUniformInd(Mat PC, int sampleStep, std::vector<int> &indices)
{
int numRows = cvRound((double)PC.rows/(double)sampleStep);
indices.resize(numRows);
Mat sampledPC = Mat(numRows, PC.cols, PC.type());
int c=0;
for (int i=0; i<PC.rows && c<numRows; i+=sampleStep)
{
indices[c] = i;
PC.row(i).copyTo(sampledPC.row(c++));
}
return sampledPC;
}
void* indexPCFlann(Mat pc)
{
Mat dest_32f;
pc.colRange(0,3).copyTo(dest_32f);
return new FlannIndex(dest_32f, cvflann::KDTreeSingleIndexParams(8));
}
void destroyFlann(void* flannIndex)
{
delete ((FlannIndex*)flannIndex);
}
// For speed purposes this function assumes that PC, Indices and Distances are created with continuous structures
void queryPCFlann(void* flannIndex, Mat& pc, Mat& indices, Mat& distances)
{
queryPCFlann(flannIndex, pc, indices, distances, 1);
}
void queryPCFlann(void* flannIndex, Mat& pc, Mat& indices, Mat& distances, const int numNeighbors)
{
Mat obj_32f;
pc.colRange(0, 3).copyTo(obj_32f);
((FlannIndex*)flannIndex)->knnSearch(obj_32f, indices, distances, numNeighbors, cvflann::SearchParams(32));
}
// uses a volume instead of an octree
// TODO: Right now normals are required.
// This is much faster than sample_pc_octree
Mat samplePCByQuantization(Mat pc, float xrange[2], float yrange[2], float zrange[2], float sampleStep, int weightByCenter)
{
std::vector< std::vector<int> > map;
int numSamplesDim = (int)(1.0/sampleStep);
float xr = xrange[1] - xrange[0];
float yr = yrange[1] - yrange[0];
float zr = zrange[1] - zrange[0];
int numPoints = 0;
map.resize((numSamplesDim+1)*(numSamplesDim+1)*(numSamplesDim+1));
// OpenMP might seem like a good idea, but it didn't speed this up for me
//#pragma omp parallel for
for (int i=0; i<pc.rows; i++)
{
const float* point = (float*)(&pc.data[i * pc.step]);
// quantize a point
const int xCell =(int) ((float)numSamplesDim*(point[0]-xrange[0])/xr);
const int yCell =(int) ((float)numSamplesDim*(point[1]-yrange[0])/yr);
const int zCell =(int) ((float)numSamplesDim*(point[2]-zrange[0])/zr);
const int index = xCell*numSamplesDim*numSamplesDim+yCell*numSamplesDim+zCell;
/*#pragma omp critical
{*/
map[index].push_back(i);
// }
}
for (unsigned int i=0; i<map.size(); i++)
{
numPoints += (map[i].size()>0);
}
Mat pcSampled = Mat(numPoints, pc.cols, CV_32F);
int c = 0;
for (unsigned int i=0; i<map.size(); i++)
{
double px=0, py=0, pz=0;
double nx=0, ny=0, nz=0;
std::vector<int> curCell = map[i];
int cn = (int)curCell.size();
if (cn>0)
{
if (weightByCenter)
{
int xCell, yCell, zCell;
double xc, yc, zc;
double weightSum = 0 ;
zCell = i % numSamplesDim;
yCell = ((i-zCell)/numSamplesDim) % numSamplesDim;
xCell = ((i-zCell-yCell*numSamplesDim)/(numSamplesDim*numSamplesDim));
xc = ((double)xCell+0.5) * (double)xr/numSamplesDim + (double)xrange[0];
yc = ((double)yCell+0.5) * (double)yr/numSamplesDim + (double)yrange[0];
zc = ((double)zCell+0.5) * (double)zr/numSamplesDim + (double)zrange[0];
for (int j=0; j<cn; j++)
{
const int ptInd = curCell[j];
float* point = (float*)(&pc.data[ptInd * pc.step]);
const double dx = point[0]-xc;
const double dy = point[1]-yc;
const double dz = point[2]-zc;
const double d = sqrt(dx*dx+dy*dy+dz*dz);
double w = 0;
if (d>EPS)
{
// it is possible to use different weighting schemes.
// inverse weigthing was just good for me
// exp( - (distance/h)**2 )
//const double w = exp(-d*d);
w = 1.0/d;
}
//float weights[3]={1,1,1};
px += w*(double)point[0];
py += w*(double)point[1];
pz += w*(double)point[2];
nx += w*(double)point[3];
ny += w*(double)point[4];
nz += w*(double)point[5];
weightSum+=w;
}
px/=(double)weightSum;
py/=(double)weightSum;
pz/=(double)weightSum;
nx/=(double)weightSum;
ny/=(double)weightSum;
nz/=(double)weightSum;
}
else
{
for (int j=0; j<cn; j++)
{
const int ptInd = curCell[j];
float* point = (float*)(&pc.data[ptInd * pc.step]);
px += (double)point[0];
py += (double)point[1];
pz += (double)point[2];
nx += (double)point[3];
ny += (double)point[4];
nz += (double)point[5];
}
px/=(double)cn;
py/=(double)cn;
pz/=(double)cn;
nx/=(double)cn;
ny/=(double)cn;
nz/=(double)cn;
}
float *pcData = (float*)(&pcSampled.data[c*pcSampled.step[0]]);
pcData[0]=(float)px;
pcData[1]=(float)py;
pcData[2]=(float)pz;
// normalize the normals
double norm = sqrt(nx*nx+ny*ny+nz*nz);
if (norm>EPS)
{
pcData[3]=(float)(nx/norm);
pcData[4]=(float)(ny/norm);
pcData[5]=(float)(nz/norm);
}
//#pragma omp atomic
c++;
curCell.clear();
}
}
map.clear();
return pcSampled;
}
void shuffle(int *array, size_t n)
{
size_t i;
for (i = 0; i < n - 1; i++)
{
size_t j = i + rand() / (RAND_MAX / (n - i) + 1);
int t = array[j];
array[j] = array[i];
array[i] = t;
}
}
// compute the standard bounding box
void computeBboxStd(Mat pc, float xRange[2], float yRange[2], float zRange[2])
{
Mat pcPts = pc.colRange(0, 3);
int num = pcPts.rows;
float* points = (float*)pcPts.data;
xRange[0] = points[0];
xRange[1] = points[0];
yRange[0] = points[1];
yRange[1] = points[1];
zRange[0] = points[2];
zRange[1] = points[2];
for ( int ind = 0; ind < num; ind++ )
{
const float* row = (float*)(pcPts.data + (ind * pcPts.step));
const float x = row[0];
const float y = row[1];
const float z = row[2];
if (x<xRange[0])
xRange[0]=x;
if (x>xRange[1])
xRange[1]=x;
if (y<yRange[0])
yRange[0]=y;
if (y>yRange[1])
yRange[1]=y;
if (z<zRange[0])
zRange[0]=z;
if (z>zRange[1])
zRange[1]=z;
}
}
Mat normalizePCCoeff(Mat pc, float scale, float* Cx, float* Cy, float* Cz, float* MinVal, float* MaxVal)
{
double minVal=0, maxVal=0;
Mat x,y,z, pcn;
pc.col(0).copyTo(x);
pc.col(1).copyTo(y);
pc.col(2).copyTo(z);
float cx = (float) cv::mean(x).val[0];
float cy = (float) cv::mean(y).val[0];
float cz = (float) cv::mean(z).val[0];
cv::minMaxIdx(pc, &minVal, &maxVal);
x=x-cx;
y=y-cy;
z=z-cz;
pcn.create(pc.rows, 3, CV_32FC1);
x.copyTo(pcn.col(0));
y.copyTo(pcn.col(1));
z.copyTo(pcn.col(2));
cv::minMaxIdx(pcn, &minVal, &maxVal);
pcn=(float)scale*(pcn)/((float)maxVal-(float)minVal);
*MinVal=(float)minVal;
*MaxVal=(float)maxVal;
*Cx=(float)cx;
*Cy=(float)cy;
*Cz=(float)cz;
return pcn;
}
Mat transPCCoeff(Mat pc, float scale, float Cx, float Cy, float Cz, float MinVal, float MaxVal)
{
Mat x,y,z, pcn;
pc.col(0).copyTo(x);
pc.col(1).copyTo(y);
pc.col(2).copyTo(z);
x=x-Cx;
y=y-Cy;
z=z-Cz;
pcn.create(pc.rows, 3, CV_32FC1);
x.copyTo(pcn.col(0));
y.copyTo(pcn.col(1));
z.copyTo(pcn.col(2));
pcn=(float)scale*(pcn)/((float)MaxVal-(float)MinVal);
return pcn;
}
Mat transformPCPose(Mat pc, double Pose[16])
{
Mat pct = Mat(pc.rows, pc.cols, CV_32F);
double R[9], t[3];
poseToRT(Pose, R, t);
#if defined _OPENMP
#pragma omp parallel for
#endif
for (int i=0; i<pc.rows; i++)
{
const float *pcData = (float*)(&pc.data[i*pc.step]);
float *pcDataT = (float*)(&pct.data[i*pct.step]);
const float *n1 = &pcData[3];
float *nT = &pcDataT[3];
double p[4] = {(double)pcData[0], (double)pcData[1], (double)pcData[2], 1};
double p2[4];
matrixProduct441(Pose, p, p2);
// p2[3] should normally be 1
if (fabs(p2[3])>EPS)
{
pcDataT[0] = (float)(p2[0]/p2[3]);
pcDataT[1] = (float)(p2[1]/p2[3]);
pcDataT[2] = (float)(p2[2]/p2[3]);
}
// If the point cloud has normals,
// then rotate them as well
if (pc.cols == 6)
{
double n[3] = { (double)n1[0], (double)n1[1], (double)n1[2] }, n2[3];
matrixProduct331(R, n, n2);
double nNorm = sqrt(n2[0]*n2[0]+n2[1]*n2[1]+n2[2]*n2[2]);
if (nNorm>EPS)
{
nT[0]=(float)(n2[0]/nNorm);
nT[1]=(float)(n2[1]/nNorm);
nT[2]=(float)(n2[2]/nNorm);
}
}
}
return pct;
}
Mat genRandomMat(int rows, int cols, double mean, double stddev, int type)
{
Mat meanMat = mean*Mat::ones(1,1,type);
Mat sigmaMat= stddev*Mat::ones(1,1,type);
RNG rng(time(0));
Mat matr(rows, cols,type);
rng.fill(matr, RNG::NORMAL, meanMat, sigmaMat);
return matr;
}
void getRandQuat(double q[4])
{
q[0] = (float)rand()/(float)(RAND_MAX);
q[1] = (float)rand()/(float)(RAND_MAX);
q[2] = (float)rand()/(float)(RAND_MAX);
q[3] = (float)rand()/(float)(RAND_MAX);
double n = sqrt(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3]);
q[0]/=n;
q[1]/=n;
q[2]/=n;
q[3]/=n;
q[0]=fabs(q[0]);
}
void getRandomRotation(double R[9])
{
double q[4];
getRandQuat(q);
quatToDCM(q, R);
}
void getRandomPose(double Pose[16])
{
double R[9], t[3];
srand((unsigned int)time(0));
getRandomRotation(R);
t[0] = (float)rand()/(float)(RAND_MAX);
t[1] = (float)rand()/(float)(RAND_MAX);
t[2] = (float)rand()/(float)(RAND_MAX);
rtToPose(R,t,Pose);
}
Mat addNoisePC(Mat pc, double scale)
{
Mat randT = genRandomMat(pc.rows,pc.cols,0,scale,CV_32FC1);
return randT + pc;
}
/*
The routines below use the eigenvectors of the local covariance matrix
to compute the normals of a point cloud.
The algorithm uses FLANN and Joachim Kopp's fast 3x3 eigenvector computations
to improve accuracy and increase speed
Also, view point flipping as in point cloud library is implemented
*/
void meanCovLocalPC(const float* pc, const int ws, const int point_count, double CovMat[3][3], double Mean[4])
{
int i;
double accu[16]={0};
// For each point in the cloud
for (i = 0; i < point_count; ++i)
{
const float* cloud = &pc[i*ws];
accu [0] += cloud[0] * cloud[0];
accu [1] += cloud[0] * cloud[1];
accu [2] += cloud[0] * cloud[2];
accu [3] += cloud[1] * cloud[1]; // 4
accu [4] += cloud[1] * cloud[2]; // 5
accu [5] += cloud[2] * cloud[2]; // 8
accu [6] += cloud[0];
accu [7] += cloud[1];
accu [8] += cloud[2];
}
for (i = 0; i < 9; ++i)
accu[i]/=(double)point_count;
Mean[0] = accu[6];
Mean[1] = accu[7];
Mean[2] = accu[8];
Mean[3] = 0;
CovMat[0][0] = accu [0] - accu [6] * accu [6];
CovMat[0][1] = accu [1] - accu [6] * accu [7];
CovMat[0][2] = accu [2] - accu [6] * accu [8];
CovMat[1][1] = accu [3] - accu [7] * accu [7];
CovMat[1][2] = accu [4] - accu [7] * accu [8];
CovMat[2][2] = accu [5] - accu [8] * accu [8];
CovMat[1][0] = CovMat[0][1];
CovMat[2][0] = CovMat[0][2];
CovMat[2][1] = CovMat[1][2];
}
void meanCovLocalPCInd(const float* pc, const int* Indices, const int ws, const int point_count, double CovMat[3][3], double Mean[4])
{
int i;
double accu[16]={0};
for (i = 0; i < point_count; ++i)
{
const float* cloud = &pc[ Indices[i] * ws ];
accu [0] += cloud[0] * cloud[0];
accu [1] += cloud[0] * cloud[1];
accu [2] += cloud[0] * cloud[2];
accu [3] += cloud[1] * cloud[1]; // 4
accu [4] += cloud[1] * cloud[2]; // 5
accu [5] += cloud[2] * cloud[2]; // 8
accu [6] += cloud[0];
accu [7] += cloud[1];
accu [8] += cloud[2];
}
for (i = 0; i < 9; ++i)
accu[i]/=(double)point_count;
Mean[0] = accu[6];
Mean[1] = accu[7];
Mean[2] = accu[8];
Mean[3] = 0;
CovMat[0][0] = accu [0] - accu [6] * accu [6];
CovMat[0][1] = accu [1] - accu [6] * accu [7];
CovMat[0][2] = accu [2] - accu [6] * accu [8];
CovMat[1][1] = accu [3] - accu [7] * accu [7];
CovMat[1][2] = accu [4] - accu [7] * accu [8];
CovMat[2][2] = accu [5] - accu [8] * accu [8];
CovMat[1][0] = CovMat[0][1];
CovMat[2][0] = CovMat[0][2];
CovMat[2][1] = CovMat[1][2];
}
CV_EXPORTS int computeNormalsPC3d(const Mat& PC, Mat& PCNormals, const int NumNeighbors, const bool FlipViewpoint, const double viewpoint[3])
{
int i;
if (PC.cols!=3 && PC.cols!=6) // 3d data is expected
{
//return -1;
CV_Error(cv::Error::BadImageSize, "PC should have 3 or 6 elements in its columns");
}
int sizes[2] = {PC.rows, 3};
int sizesResult[2] = {PC.rows, NumNeighbors};
float* dataset = new float[PC.rows*3];
float* distances = new float[PC.rows*NumNeighbors];
int* indices = new int[PC.rows*NumNeighbors];
for (i=0; i<PC.rows; i++)
{
const float* src = (float*)(&PC.data[i*PC.step]);
float* dst = (float*)(&dataset[i*3]);
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
}
Mat PCInput(2, sizes, CV_32F, dataset, 0);
void* flannIndex = indexPCFlann(PCInput);
Mat Indices(2, sizesResult, CV_32S, indices, 0);
Mat Distances(2, sizesResult, CV_32F, distances, 0);
queryPCFlann(flannIndex, PCInput, Indices, Distances, NumNeighbors);
destroyFlann(flannIndex);
flannIndex = 0;
PCNormals = Mat(PC.rows, 6, CV_32F);
for (i=0; i<PC.rows; i++)
{
double C[3][3], mu[4];
const float* pci = &dataset[i*3];
float* pcr = (float*)(&PCNormals.data[i*PCNormals.step]);
double nr[3];
int* indLocal = &indices[i*NumNeighbors];
// compute covariance matrix
meanCovLocalPCInd(dataset, indLocal, 3, NumNeighbors, C, mu);
// eigenvectors of covariance matrix
Mat cov(3, 3, CV_64F), eigVect, eigVal;
double* covData = (double*)cov.data;
covData[0] = C[0][0];
covData[1] = C[0][1];
covData[2] = C[0][2];
covData[3] = C[1][0];
covData[4] = C[1][1];
covData[5] = C[1][2];
covData[6] = C[2][0];
covData[7] = C[2][1];
covData[8] = C[2][2];
eigen(cov, eigVal, eigVect);
Mat lowestEigVec;
//the eigenvector for the lowest eigenvalue is in the last row
eigVect.row(eigVect.rows - 1).copyTo(lowestEigVec);
double* eigData = (double*)lowestEigVec.data;
nr[0] = eigData[0];
nr[1] = eigData[1];
nr[2] = eigData[2];
pcr[0] = pci[0];
pcr[1] = pci[1];
pcr[2] = pci[2];
if (FlipViewpoint)
{
flipNormalViewpoint(pci, viewpoint[0], viewpoint[1], viewpoint[2], &nr[0], &nr[1], &nr[2]);
}
pcr[3] = (float)nr[0];
pcr[4] = (float)nr[1];
pcr[5] = (float)nr[2];
}
delete[] indices;
delete[] distances;
delete[] dataset;
return 1;
}
} // namespace ppf_match_3d
} // namespace cv