Skip to content

Commit acb88d7

Browse files
committed
Wrap ICP for Python
1 parent b11ce40 commit acb88d7

File tree

3 files changed

+73
-21
lines changed

3 files changed

+73
-21
lines changed

modules/surface_matching/include/opencv2/surface_matching/icp.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,17 @@ namespace ppf_match_3d
7777
* 5. Linearization of Point-to-Plane metric by Kok Lim Low:
7878
* https://www.comp.nus.edu.sg/~lowkl/publications/lowk_point-to-plane_icp_techrep.pdf
7979
*/
80-
class CV_EXPORTS ICP
80+
class CV_EXPORTS_W ICP
8181
{
8282
public:
8383

84-
enum ICP_SAMPLING_TYPE
84+
CV_WRAP enum
8585
{
86-
ICP_SAMPLING_TYPE_UNIFORM,
87-
ICP_SAMPLING_TYPE_GELFAND
86+
ICP_SAMPLING_TYPE_UNIFORM = 0,
87+
ICP_SAMPLING_TYPE_GELFAND = 1
8888
};
8989

90-
ICP()
90+
CV_WRAP ICP()
9191
{
9292
m_tolerance = 0.005f;
9393
m_rejectionScale = 2.5f;
@@ -114,7 +114,7 @@ class CV_EXPORTS ICP
114114
applied. Leave it as 0.
115115
* @param [in] numMaxCorr Currently this parameter is ignored and only PickyICP is applied. Leave it as 1.
116116
*/
117-
ICP(const int iterations, const float tolerence=0.05, const float rejectionScale=2.5, const int numLevels=6, const ICP_SAMPLING_TYPE sampleType = ICP_SAMPLING_TYPE_UNIFORM, const int numMaxCorr=1)
117+
CV_WRAP ICP(const int iterations, const float tolerence = 0.05f, const float rejectionScale = 2.5f, const int numLevels = 6, const int sampleType = ICP::ICP_SAMPLING_TYPE_UNIFORM, const int numMaxCorr = 1)
118118
{
119119
m_tolerance = tolerence;
120120
m_numNeighborsCorr = numMaxCorr;
@@ -136,7 +136,7 @@ class CV_EXPORTS ICP
136136
*
137137
* \details It is assumed that the model is registered on the scene. Scene remains static, while the model transforms. The output poses transform the models onto the scene. Because of the point to plane minimization, the scene is expected to have the normals available. Expected to have the normals (Nx6).
138138
*/
139-
int registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residual, double pose[16]);
139+
CV_WRAP int registerModelToScene(const Mat& srcPC, const Mat& dstPC, CV_OUT double& residual, CV_OUT Matx44d& pose);
140140

141141
/**
142142
* \brief Perform registration with multiple initial poses
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import cv2
2+
import numpy as np
3+
4+
def rotation(theta):
5+
tx, ty, tz = theta
6+
7+
Rx = np.array([[1, 0, 0], [0, np.cos(tx), -np.sin(tx)], [0, np.sin(tx), np.cos(tx)]])
8+
Ry = np.array([[np.cos(ty), 0, -np.sin(ty)], [0, 1, 0], [np.sin(ty), 0, np.cos(ty)]])
9+
Rz = np.array([[np.cos(tz), -np.sin(tz), 0], [np.sin(tz), np.cos(tz), 0], [0, 0, 1]])
10+
11+
return np.dot(Rx, np.dot(Ry, Rz))
12+
13+
width = 20
14+
height = 10
15+
max_deg = np.pi / 12
16+
17+
cloud, rotated_cloud = [None]*3, [None]*3
18+
retval, residual, pose = [None]*3, [None]*3, [None]*3
19+
noise = np.random.normal(0.0, 0.1, height * width * 3).reshape((-1, 3))
20+
noise2 = np.random.normal(0.0, 1.0, height * width)
21+
22+
x, y = np.meshgrid(
23+
range(-width/2, width/2),
24+
range(-height/2, height/2),
25+
sparse=False, indexing='xy'
26+
)
27+
z = np.zeros((height, width))
28+
29+
cloud[0] = np.dstack((x, y, z)).reshape((-1, 3)).astype(np.float32)
30+
cloud[1] = noise.astype(np.float32) + cloud[0]
31+
cloud[2] = cloud[1]
32+
cloud[2][:, 2] += noise2.astype(np.float32)
33+
34+
R = rotation([
35+
0, #np.random.uniform(-max_deg, max_deg),
36+
np.random.uniform(-max_deg, max_deg),
37+
0, #np.random.uniform(-max_deg, max_deg)
38+
])
39+
t = np.zeros((3, 1))
40+
Rt = np.vstack((
41+
np.hstack((R, t)),
42+
np.array([0, 0, 0, 1])
43+
)).astype(np.float32)
44+
45+
icp = cv2.ppf_match_3d.ICP(100)
46+
47+
I = np.eye(4)
48+
print("Unaligned error:\t%.6f" % np.linalg.norm(I - Rt))
49+
for i in range(3):
50+
rotated_cloud[i] = np.matmul(Rt[0:3,0:3], cloud[i].T).T + Rt[:3,3].T
51+
retval[i], residual[i], pose[i] = icp.registerModelToScene(rotated_cloud[i], cloud[i])
52+
print("ICP error:\t\t%.6f" % np.linalg.norm(I - np.matmul(pose[0], Rt)))

modules/surface_matching/src/icp.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ static hashtable_int* getHashtable(int* data, size_t length, int numMaxElement)
293293
}
294294

295295
// source point clouds are assumed to contain their normals
296-
int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residual, double pose[16])
296+
int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residual, Matx44d& pose)
297297
{
298298
int n = srcPC.rows;
299299

@@ -320,7 +320,7 @@ int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residu
320320
Mat dstPC0 = dstTemp;
321321

322322
// initialize pose
323-
matrixIdentity(4, pose);
323+
matrixIdentity(4, pose.val);
324324

325325
void* flann = indexPCFlann(dstPC0);
326326
Mat M = Mat::eye(4,4,CV_64F);
@@ -339,7 +339,7 @@ int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residu
339339
const int MaxIterationsPyr = cvRound((double)m_maxIterations/(level+1));
340340

341341
// Obtain the sampled point clouds for this level: Also rotates the normals
342-
Mat srcPCT = transformPCPose(srcPC0, pose);
342+
Mat srcPCT = transformPCPose(srcPC0, pose.val);
343343

344344
const int sampleStep = cvRound((double)n/(double)numSamples);
345345
std::vector<int> srcSampleInd;
@@ -500,11 +500,11 @@ int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residu
500500
}
501501

502502
double TempPose[16];
503-
matrixProduct44(PoseX, pose, TempPose);
503+
matrixProduct44(PoseX, pose.val, TempPose);
504504

505505
// no need to copy the last 4 rows
506506
for (int c=0; c<12; c++)
507-
pose[c] = TempPose[c];
507+
pose.val[c] = TempPose[c];
508508

509509
residual = tempResidual;
510510

@@ -519,17 +519,17 @@ int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, double& residu
519519
}
520520

521521
// Pose(1:3, 4) = Pose(1:3, 4)./scale;
522-
pose[3] = pose[3]/scale + meanAvg[0];
523-
pose[7] = pose[7]/scale + meanAvg[1];
524-
pose[11] = pose[11]/scale + meanAvg[2];
522+
pose.val[3] = pose.val[3]/scale + meanAvg[0];
523+
pose.val[7] = pose.val[7]/scale + meanAvg[1];
524+
pose.val[11] = pose.val[11]/scale + meanAvg[2];
525525

526526
// In MATLAB this would be : Pose(1:3, 4) = Pose(1:3, 4)./scale + meanAvg' - Pose(1:3, 1:3)*meanAvg';
527527
double Rpose[9], Cpose[3];
528-
poseToR(pose, Rpose);
528+
poseToR(pose.val, Rpose);
529529
matrixProduct331(Rpose, meanAvg, Cpose);
530-
pose[3] -= Cpose[0];
531-
pose[7] -= Cpose[1];
532-
pose[11] -= Cpose[2];
530+
pose.val[3] -= Cpose[0];
531+
pose.val[7] -= Cpose[1];
532+
pose.val[11] -= Cpose[2];
533533

534534
residual = tempResidual;
535535

@@ -542,10 +542,10 @@ int ICP::registerModelToScene(const Mat& srcPC, const Mat& dstPC, std::vector<Po
542542
{
543543
for (size_t i=0; i<poses.size(); i++)
544544
{
545-
double poseICP[16]={0};
545+
Matx44d poseICP = Matx44d::eye();
546546
Mat srcTemp = transformPCPose(srcPC, poses[i]->pose);
547547
registerModelToScene(srcTemp, dstPC, poses[i]->residual, poseICP);
548-
poses[i]->appendPose(poseICP);
548+
poses[i]->appendPose(poseICP.val);
549549
}
550550
return 0;
551551
}

0 commit comments

Comments
 (0)