SLAMflex SE  0.1.0
SLAMflex provides detection and tracking of dominant planes for smartphone devices. This plane can then be used to show AR content relative to the plane orientation. The detection of plane is performed in the field of view of the smartphone camera. In subsequent frames it is tracked. The interface returns the plane position and orientation.
SmallBlurryImage.cpp
Go to the documentation of this file.
1 // Copyright 2008 Isis Innovation Limited
2 #include "SmallBlurryImage.h"
3 #include "utility.h"
4 #include "convolution.h"
5 #include "vision.h"
6 #include "se2.h"
7 #include "Cholesky.h"
8 #include "wls.h"
9 
10 using namespace CVD;
11 using namespace std;
12 
14 
16 {
17  mbMadeJacs = false;
18  MakeFromKF(kf, dBlur);
19 }
20 
22 {
23  mbMadeJacs = false;
24 }
25 
26 // Make a SmallBlurryImage from a KeyFrame This fills in the mimSmall
27 // image (Which is just a small un-blurred version of the KF) and
28 // mimTemplate (which is a floating-point, zero-mean blurred version
29 // of the above)
30 void SmallBlurryImage::MakeFromKF(KeyFrame &kf, double dBlur)
31 {
32  if(mirSize[0] == -1)
33  mirSize = kf.aLevels[3].im.size() / 2;
34  mbMadeJacs = false;
35 
36  mimSmall.resize(mirSize);
37  mimTemplate.resize(mirSize);
38 
39  mbMadeJacs = false;
40  halfSample(kf.aLevels[3].im, mimSmall);
41  ImageRef ir;
42  unsigned int nSum = 0;
43  do
44  nSum += mimSmall[ir];
45  while(ir.next(mirSize));
46 
47  float fMean = ((float) nSum) / mirSize.area();
48 
49  ir.home();
50  do
51  mimTemplate[ir] = mimSmall[ir] - fMean;
52  while(ir.next(mirSize));
53 
54  convolveGaussian(mimTemplate, dBlur);
55 }
56 
57 // Make the jacobians (actually, no more than a gradient image)
58 // of the blurred template
60 {
61  mimImageJacs.resize(mirSize);
62  // Fill in the gradient image
63  ImageRef ir;
64  do
65  {
66  Vector<2> &v2Grad = mimImageJacs[ir];
67  if(mimTemplate.in_image_with_border(ir,1))
68  {
69  v2Grad[0] = mimTemplate[ir + ImageRef(1,0)] - mimTemplate[ir - ImageRef(1,0)];
70  v2Grad[1] = mimTemplate[ir + ImageRef(0,1)] - mimTemplate[ir - ImageRef(0,1)];
71  // N.b. missing 0.5 factor in above, this will be added later.
72  }
73  else
74  v2Grad = Zeros;
75  }
76  while(ir.next(mirSize));
77  mbMadeJacs = true;
78 };
79 
80 // Calculate the zero-mean SSD between one image and the next.
81 // Since both are zero mean already, just calculate the SSD...
83 {
84  double dSSD = 0.0;
85  ImageRef ir;
86  do
87  {
88  double dDiff = mimTemplate[ir] - other.mimTemplate[ir];
89  dSSD += dDiff * dDiff;
90  }
91  while(ir.next(mirSize));
92  return dSSD;
93 }
94 
95 
96 // Find an SE2 which best aligns an SBI to a target
97 // Do this by ESM-tracking a la Benhimane & Malis
98 pair<SE2<>,double> SmallBlurryImage::IteratePosRelToTarget(SmallBlurryImage &other, int nIterations)
99 {
100  SE2<> se2CtoC;
101  SE2<> se2WfromC;
102  ImageRef irCenter = mirSize / 2;
103  se2WfromC.get_translation() = vec(irCenter);
104 
105  pair<SE2<>, double> result_pair;
106  if(!other.mbMadeJacs)
107  {
108  cerr << "You spanner, you didn't make the jacs for the target." << endl;
109  assert(other.mbMadeJacs);
110  };
111 
112  double dMeanOffset = 0.0;
113  Vector<4> v4Accum;
114 
115  Vector<10> v10Triangle;
116  Image<float> imWarped(mirSize);
117 
118  double dFinalScore = 0.0;
119  for(int it = 0; it<nIterations; it++)
120  {
121  dFinalScore = 0.0;
122  v4Accum = Zeros;
123  v10Triangle = Zeros; // Holds the bottom-left triangle of JTJ
124  Vector<4> v4Jac;
125  v4Jac[3] = 1.0;
126 
127  SE2<> se2XForm = se2WfromC * se2CtoC * se2WfromC.inverse();
128 
129  // Make the warped current image template:
130  Vector<2> v2Zero = Zeros;
131  CVD::transform(mimTemplate, imWarped, se2XForm.get_rotation().get_matrix(), se2XForm.get_translation(), v2Zero, -9e20f);
132 
133  // Now compare images, calc differences, and current image jacobian:
134  ImageRef ir;
135  do
136  {
137  if(!imWarped.in_image_with_border(ir,1))
138  continue;
139  float l,r,u,d,here;
140  l = imWarped[ir - ImageRef(1,0)];
141  r = imWarped[ir + ImageRef(1,0)];
142  u = imWarped[ir - ImageRef(0,1)];
143  d = imWarped[ir + ImageRef(0,1)];
144  here = imWarped[ir];
145  if(l + r + u + d + here < -9999.9) // This means it's out of the image; c.f. the -9e20f param to transform.
146  continue;
147 
148  Vector<2> v2CurrentGrad;
149  v2CurrentGrad[0] = r - l; // Missing 0.5 factor
150  v2CurrentGrad[1] = d - u;
151 
152  Vector<2> v2SumGrad = 0.25 * (v2CurrentGrad + other.mimImageJacs[ir]);
153  // Why 0.25? This is from missing 0.5 factors: One for
154  // the fact we average two gradients, the other from
155  // each gradient missing a 0.5 factor.
156 
157  v4Jac[0] = v2SumGrad[0];
158  v4Jac[1] = v2SumGrad[1];
159  v4Jac[2] = -(ir.y - irCenter.y) * v2SumGrad[0] + (ir.x - irCenter.x) * v2SumGrad[1];
160  // v4Jac[3] = 1.0;
161 
162  double dDiff = imWarped[ir] - other.mimTemplate[ir] + dMeanOffset;
163  dFinalScore += dDiff * dDiff;
164 
165  v4Accum += dDiff * v4Jac;
166 
167  // Speedy fill of the LL triangle of JTJ:
168  double *p = &v10Triangle[0];
169  *p++ += v4Jac[0] * v4Jac[0];
170  *p++ += v4Jac[1] * v4Jac[0];
171  *p++ += v4Jac[1] * v4Jac[1];
172  *p++ += v4Jac[2] * v4Jac[0];
173  *p++ += v4Jac[2] * v4Jac[1];
174  *p++ += v4Jac[2] * v4Jac[2];
175  *p++ += v4Jac[0];
176  *p++ += v4Jac[1];
177  *p++ += v4Jac[2];
178  *p++ += 1.0;
179  }
180  while(ir.next(mirSize));
181 
182  Vector<4> v4Update;
183 
184  // Solve for JTJ-1JTv;
185  {
186  Matrix<4> m4;
187  int v=0;
188  for(int j=0; j<4; j++)
189  for(int i=0; i<=j; i++)
190  m4[j][i] = m4[i][j] = v10Triangle[v++];
191  Cholesky<4> chol(m4);
192  v4Update = chol.backsub(v4Accum);
193  }
194 
195  SE2<> se2Update;
196  se2Update.get_translation() = -v4Update.slice<0,2>();
197  se2Update.get_rotation() = SO2<>::exp(-v4Update[2]);
198  se2CtoC = se2CtoC * se2Update;
199  dMeanOffset -= v4Update[3];
200  }
201 
202  result_pair.first = se2CtoC;
203  result_pair.second = dFinalScore;
204  return result_pair;
205 }
206 
207 
208 // What is the 3D camera rotation (zero trans) SE3<> which causes an
209 // input image SO2 rotation?
211 {
212  // Do this by projecting two points, and then iterating the SE3<> (SO3
213  // actually) until convergence. It might seem stupid doing this so
214  // precisely when the whole SE2-finding is one big hack, but hey.
215 
216  camera.SetImageSize(mirSize);
217 
218  Vector<2> av2Turned[2]; // Our two warped points in pixels
219  av2Turned[0] = vec(mirSize / 2) + se2 * vec(ImageRef(5,0));
220  av2Turned[1] = vec(mirSize / 2) + se2 * vec(ImageRef(-5,0));
221 
222  Vector<3> av3OrigPoints[2]; // 3D versions of these points.
223  av3OrigPoints[0] = unproject(camera.UnProject(vec(mirSize / 2) + vec(ImageRef(5,0))));
224  av3OrigPoints[1] = unproject(camera.UnProject(vec(mirSize / 2) + vec(ImageRef(-5,0))));
225 
226  SO3<> so3;
227  for(int it = 0; it<3; it++)
228  {
229  WLS<3> wls; // lazy; no need for the 'W'
230  wls.add_prior(10.0);
231  for(int i=0; i<2; i++)
232  {
233  // Project into the image to find error
234  Vector<3> v3Cam = so3 * av3OrigPoints[i];
235  Vector<2> v2Implane = project(v3Cam);
236  Vector<2> v2Pixels = camera.Project(v2Implane);
237  Vector<2> v2Error = av2Turned[i] - v2Pixels;
238 
239  Matrix<2> m2CamDerivs = camera.GetProjectionDerivs();
240  Matrix<2,3> m23Jacobian;
241  double dOneOverCameraZ = 1.0 / v3Cam[2];
242  for(int m=0; m<3; m++)
243  {
244  const Vector<3> v3Motion = SO3<>::generator_field(m, v3Cam);
245  Vector<2> v2CamFrameMotion;
246  v2CamFrameMotion[0] = (v3Motion[0] - v3Cam[0] * v3Motion[2] * dOneOverCameraZ) * dOneOverCameraZ;
247  v2CamFrameMotion[1] = (v3Motion[1] - v3Cam[1] * v3Motion[2] * dOneOverCameraZ) * dOneOverCameraZ;
248  m23Jacobian.T()[m] = m2CamDerivs * v2CamFrameMotion;
249  };
250  wls.add_mJ(v2Error[0], m23Jacobian[0], 1.0);
251  wls.add_mJ(v2Error[1], m23Jacobian[1], 1.0);
252  };
253 
254  wls.compute();
255  Vector<3> v3Res = wls.get_mu();
256  so3 = SO3<>::exp(v3Res) * so3;
257  };
258 
259  SE3<> se3Result;
260  se3Result.get_rotation() = so3;
261  return se3Result;
262 }
263 
264 
265 
266 
267 
268 
269 
270 
271 
272 
SE2 inverse() const
compute the inverse of the transformation
Definition: se2.h:81
int y
The y co-ordinate.
Definition: image_ref.h:180
int x
The x co-ordinate.
Definition: image_ref.h:179
void halfSample(const BasicImage< T > &in, BasicImage< T > &out)
Definition: vision.h:103
Definition: so3.h:39
std::pair< SE2<>, double > IteratePosRelToTarget(SmallBlurryImage &other, int nIterations=10)
void MakeFromKF(KeyFrame &kf, double dBlur=2.5)
SO3< Precision > & get_rotation()
Returns the rotation part of the transformation as a SO3.
Definition: se3.h:64
Vector< 2 > Project(const Vector< 2 > &camframe)
Definition: ATANCamera.cpp:109
Vector< Size, Precision > backsub(const Vector< Size2, P2, B2 > &v) const
Definition: Cholesky.h:126
static CVD::ImageRef mirSize
TooN::Vector< 2 > vec(const ImageRef &ir)
void convolveGaussian(BasicImage< T > &I, double sigma, double sigmas=3.0)
Definition: convolution.h:415
CVD::Image< CVD::byte > im
Definition: KeyFrame.h:59
Vector<(Size==Dynamic?Dynamic:Size+1), Precision > unproject(const Vector< Size, Precision, Base > &v)
Definition: helpers.h:166
void add_mJ(Precision m, const Vector< Size, Precision, B2 > &J, Precision weight=1)
Definition: wls.h:100
int area() const
Area (product of x and y; signed)
Definition: image_ref.h:407
Definition: se2.h:52
Definition: abs.h:24
SO2< Precision > & get_rotation()
Returns the rotation part of the transformation as a SO2.
Definition: se2.h:60
CVD::Image< float > mimTemplate
Vector< 2 > UnProject(const Vector< 2 > &imframe)
Definition: ATANCamera.cpp:125
Vector< Size, Precision > & get_mu()
Returns the update. With no prior, this is the result of .
Definition: wls.h:202
bool next(const ImageRef &max)
Definition: image_ref.h:220
Level aLevels[LEVELS]
Definition: KeyFrame.h:84
Definition: se3.h:50
int transform(const BasicImage< S > &in, BasicImage< T > &out, const TooN::Matrix< 2 > &M, const TooN::Vector< 2 > &inOrig, const TooN::Vector< 2 > &outOrig, const T defaultValue=T())
Definition: vision.h:304
Matrix< R, C, P > exp(const Matrix< R, C, P, B > &m)
Definition: helpers.h:284
CVD::Image< Vector< 2 > > mimImageJacs
ImageRef size() const
What is the size of this image?
Definition: image.h:370
bool in_image_with_border(const ImageRef &ir, int border) const
Definition: image.h:271
ImageRef ir(const TooN::Vector< 2 > &v)
Vector< 2, Precision > & get_translation()
Returns the translation part of the transformation as a Vector.
Definition: se2.h:64
void SetImageSize(Vector< 2 > v2ImageSize)
Definition: ATANCamera.cpp:21
void compute()
Definition: wls.h:180
void home()
Resets the ImageRef to (0,0)
Definition: image_ref.h:240
double ZMSSD(SmallBlurryImage &other)
void add_prior(Precision val)
Definition: wls.h:70
Matrix< 2, 2 > GetProjectionDerivs()
Definition: ATANCamera.cpp:172
static Operator< Internal::Zero > Zeros
Definition: objects.h:727
Vector<(Size==Dynamic?Dynamic:Size-1), Precision > project(const Vector< Size, Precision, Base > &v)
Definition: helpers.h:157
static SE3 SE3fromSE2(SE2<> se2, ATANCamera camera)
Definition: wls.h:48