forked from bkarwoski/pnp
-
Notifications
You must be signed in to change notification settings - Fork 1
/
pnp_ransac.cpp
305 lines (236 loc) · 8.95 KB
/
pnp_ransac.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#include <iostream>
#include <ceres/ceres.h>
#include <utils/mlibtime.h>
#include <utils/random.h>
#include <pnp_ransac.h>
#include <p4p.h>
using std::cout;using std::endl;
namespace cvl {
PoseD pnp_ransac(const std::vector<cvl::Vector3D>& xs,
const std::vector<cvl::Vector2D>& yns,
PnpParams params){
PNP pnp(xs,yns,params);
return pnp.compute();
}
/**
* @brief evaluate_inlier_set
* @param xs
* @param yns
* @param threshold
* @param pose
* @param best_inliers
* @return number of inliers if greater than best_inliers, otherwize slightly less...
* set best_inliers to 0 to compute the exact count...
*
*/
uint evaluate_inlier_set(const std::vector<cvl::Vector3D>& xs,
const std::vector<cvl::Vector2D>& yns,
double threshold,
PoseD pose,
uint best_inliers){
// this is the by far slowest part of the system!
uint inliers=0;
Matrix4x4D M=pose.get4x4(); // much faster...
double threshold_squared=threshold*threshold; // we are comparing to the square of it after all...
for(uint i=0;i<xs.size();++i){
//cout<<((pose*xs[i]).dehom() - yns[i]).squaredNorm()<<" "<<threshold_squared<<endl;
cvl::Vector4D X=xs[i].homogeneous(); // yes even with the extra cost here...
// Vector3D XR=(M*X).dehom(); // technically based on how 4x4 etc work, no dehom required
Vector4D XR=(M*X);
assert(std::abs(XR[3]-1)<1e-12);
double x=XR[0];
double y=XR[1];
// any negative value is behind the camera, those are outliers by any definition!
// however very distant points behave fairly well... even with the abs... so break is needed
double iz=1.0/XR[2];
if(iz<0) continue;
double err1=x *iz - yns[i](0);
double err2=y *iz - yns[i](1);
double err=err1*err1 + err2*err2;
inliers += (err < threshold_squared) ? 1 : 0;
//mle += std::min(errors[i],thr);// use this to compute mle instead...
// highest number of inliers possible at this point. inliers + (xs.size()) -i
if(((xs.size()-i +inliers)<best_inliers)) break;
}
uint min_inliers = 4;
inliers = std::min(min_inliers, inliers);
return inliers;
}
/**
* @brief The PnPReprojectionError class pnp cost for ceres
*
* Note:
* - The cost is will become vectorized if compiled with optimization
*
*/
class PnPReprojectionError
{
public:
/**
* @brief PnPReprojectionError
* @param xs
* @param yns
*/
PnPReprojectionError(const std::vector<Vector3D>& xs,
const std::vector<Vector2D>& yns) {
this->xs=xs;
this->yns=yns;
}
template <typename T>
/**
* @brief operator () autodiff enabled error
* @param rotation
* @param translation
* @param residuals
* @return
*/
bool operator()(const T* const rotation, const T* const translation,T* residuals) const
{
// Get camera rotation and translation
cvl::Pose<T> P(rotation,translation);
//cvl::Matrix3x3<T> R=P.getR();
cvl::Matrix4x4<T> M=P.get4x4();
//cvl::Vector3<T> tr(translation,true);
for (uint i = 0; i < xs.size(); ++i) {
cvl::Vector4<T> x=xs[i].homogeneous();
cvl::Vector4<T> xr=M*x;
T iz=ceres::abs(T(1.0/xr[2]));
residuals[0] = xr[0] *iz - T(yns[i][0]);
residuals[1] = xr[1] *iz - T(yns[i][1]);
residuals+=2;
}
return true;
}
/// the 3d point observations
std::vector<Vector3D> xs;
/// the pinhole normalized image observations
std::vector<Vector2D> yns;
/**
* @brief Create Autodiff error factory
* @param inlier_xs
* @param inlier_yns
* @return
*/
static ceres::CostFunction* Create(const std::vector<Vector3D>& inlier_xs,
const std::vector<Vector2D>& inlier_yns ){
return new ceres::AutoDiffCostFunction<PnPReprojectionError, ceres::DYNAMIC, 4,3>(
new PnPReprojectionError(inlier_xs,inlier_yns), inlier_xs.size()*2);
}
};
Vector4<uint> get4RandomInRange0(uint max){
Vector4<uint> indexes;
//todo verify the fast one!
/*
n=0;
while(n<3){
uint val=randui<int>(0,max-1);
for(int i=0;i<n;++i)
if(indexes[i]==val) continue;
indexes[n++]=val;
}*/
// for large numbers in smallish sets, sorting is faster
std::set<uint> set;
assert(4<=max);
while(set.size()<4)
set.insert(mlib::randui<int>(0,max-1));
int n=0;
for(uint i:set){
indexes[n++]=i;
}
return indexes;
}
PoseD PNP::compute(){
double err_portion = ((double)xs.size() - best_inliers) / ((double)xs.size());
uint iters=params.get_iterations(params.min_probability, err_portion, 4, params.max_iterations);
uint i;
for(i=0;i<iters;++i){
// pick 4 at random
// will always succeed, returns identity on degeneracy...
PoseD pose=p4p(xs,yns,get4RandomInRange0(xs.size()));
assert(pose.isnormal());
if(!pose.isnormal()) continue;
// evaluate inlier set
uint inliers=evaluate_inlier_set(xs,yns,params.threshold,pose,best_inliers);
if(inliers>best_inliers){
best_inliers=inliers;
best_pose=pose;
// recompute only when neccessary its expensive...
err_portion = ((double)xs.size() - best_inliers) / ((double)xs.size());
iters=params.get_iterations(params.min_probability, err_portion, 4, params.max_iterations);
}
}
total_iters=i;
// refine pose, if possible...
if(best_inliers>3){
refine();
}
return best_pose; // will be identity if a complete failure...
}
/**
* @brief PNP::refine
* since we expect a high noise, low outlier ratio solution(<50%), we should refine using a cutoff loss twice...
*/
void PNP::refine(){
std::vector<Vector3D> inlier_xs;inlier_xs.reserve(xs.size());
std::vector<Vector2D> inlier_yns;inlier_yns.reserve(xs.size());
inliers.resize(xs.size(),0);
double thr=params.threshold*params.threshold;
{
// think about if you can use an explicit cutoff loss here...
// tests show its faster to separate the inliers and call it twice though...
inlier_xs.clear();
inlier_yns.clear();
for(uint i=0;i<xs.size();++i){
if(((best_pose*xs[i]).dehom() - yns[i]).squaredNorm()>thr) continue;
inlier_xs.push_back(xs[i]);
inlier_yns.push_back(yns[i]);
inliers[i]=1;
}
ceres::Problem problem;
ceres::LossFunction* loss= new ceres::HuberLoss(1.0);
problem.AddResidualBlock(PnPReprojectionError::Create(inlier_xs,inlier_yns),loss,best_pose.getRRef(),best_pose.getTRef());
ceres::LocalParameterization* qp = new ceres::QuaternionParameterization;
problem.SetParameterization(best_pose.getRRef(), qp);
ceres::Solver::Options options;{
options.linear_solver_type = ceres::DENSE_SCHUR;
options.max_num_iterations=5;
options.function_tolerance=1e-6; // default 1e-6
options.gradient_tolerance=1e-6; //default 1e-4*function_tolerance
}
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
//out<<"Report 1: \n"<<summary.FullReport()<<endl;
}
// check somehow if there is a big difference in the inliers?
{
// think about if you can use an explicit cutoff loss here...
// tests show its faster to separate the inliers and call it twice though...
inlier_xs.clear();
inlier_yns.clear();
int deltas=0;
for(uint i=0;i<xs.size();++i){
bool inlier=((best_pose*xs[i]).dehom() - yns[i]).squaredNorm()<thr;
if(inlier ^ (inliers[i]==1)) deltas++;
if(!inlier) continue;
inlier_xs.push_back(xs[i]);
inlier_yns.push_back(yns[i]);
}
// if to few changes hav occured, dont do a second refine...
if(deltas<0.05*inlier_xs.size()) return;
ceres::Problem problem;
ceres::LossFunction* loss=nullptr;//
problem.AddResidualBlock(PnPReprojectionError::Create(inlier_xs,inlier_yns),loss,best_pose.getRRef(),best_pose.getTRef());
ceres::LocalParameterization* qp = new ceres::QuaternionParameterization;
problem.SetParameterization(best_pose.getRRef(), qp);
ceres::Solver::Options options;{
options.linear_solver_type = ceres::DENSE_SCHUR;
options.max_num_iterations=3;
options.function_tolerance=1e-8; // default 1e-6
options.gradient_tolerance=1e-8; //default 1e-4*function_tolerance
}
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
//cout<<"Report 1: \n"<<summary.FullReport()<<endl;
}
}
} // end namespace cvl