import java.util.List; import processing.core.PVector; import org.opencv.core.Mat; import org.opencv.core.CvType; import org.opencv.core.Core; class TwoDThreeD { // default focal length, well suited for most webcams float f = 700; // intrisic camera matrix float [][] K = {{f, 0, 0}, {0, f, 0}, {0, 0, 1}}; float [][] invK; PVector invK_r1, invK_r2, invK_r3; Mat opencv_A, w, u, vt; double [][] V; // Real physical coordinates of the Lego board in mm //float boardSize = 380.f; // large Duplo board // float boardSize = 255.f; // smaller Lego board // the 3D coordinates of the physical board corners, clockwise float [][] physicalCorners = { {-128, 128, 0, 1}, {128, 128, 0, 1}, {128, -128, 0, 1}, {-128, -128, 0, 1} }; //Filtering variables: low-pass filter based on arFilterTrans from ARToolKit v5 */ float[] q; float sampleRate; float cutOffFreq; float alpha; public TwoDThreeD(int width, int height, float sampleRate) { // set the offset to the center of the webcam image K[0][2] = 0.5f * width; K[1][2] = 0.5f * height; //compute inverse of K Mat opencv_K= new Mat(3, 3, CvType.CV_32F); opencv_K.put(0, 0, K[0][0]); opencv_K.put(0, 1, K[0][1]); opencv_K.put(0, 2, K[0][2]); opencv_K.put(1, 0, K[1][0]); opencv_K.put(1, 1, K[1][1]); opencv_K.put(1, 2, K[1][2]); opencv_K.put(2, 0, K[2][0]); opencv_K.put(2, 1, K[2][1]); opencv_K.put(2, 2, K[2][2]); Mat opencv_invK=opencv_K.inv(); invK = new float[][]{ { (float)opencv_invK.get(0, 0)[0], (float)opencv_invK.get(0, 1)[0], (float)opencv_invK.get(0, 2)[0] }, { (float)opencv_invK.get(1, 0)[0], (float)opencv_invK.get(1, 1)[0], (float)opencv_invK.get(1, 2)[0] }, { (float)opencv_invK.get(2, 0)[0], (float)opencv_invK.get(2, 1)[0], (float)opencv_invK.get(2, 2)[0] }}; invK_r1=new PVector(invK[0][0], invK[0][1], invK[0][2]); invK_r2=new PVector(invK[1][0], invK[1][1], invK[1][2]); invK_r3=new PVector(invK[2][0], invK[2][1], invK[2][2]); opencv_A=new Mat(12, 9, CvType.CV_32F); w=new Mat(); u=new Mat(); vt=new Mat(); V= new double[9][9]; q=new float[4]; q[3]=1; this.sampleRate=sampleRate; if (sampleRate>0) { cutOffFreq=sampleRate/2; alpha= (1/sampleRate)/(1/sampleRate + 1/cutOffFreq); } } PVector get3DRotations(List points2D) { // 1- Solve the extrinsic matrix from the projected 2D points double[][] E = solveExtrinsicMatrix(points2D); // 2 - Re-build a proper 3x3 rotation matrix from the camera's // extrinsic matrix E PVector firstColumn=new PVector((float)E[0][0], (float)E[1][0], (float)E[2][0]); PVector secondColumn=new PVector((float)E[0][1], (float)E[1][1], (float)E[2][1]); firstColumn.normalize(); secondColumn.normalize(); PVector thirdColumn=firstColumn.cross(secondColumn); float [][] rotationMatrix={{firstColumn.x, secondColumn.x, thirdColumn.x}, {firstColumn.y, secondColumn.y, thirdColumn.y}, {firstColumn.z, secondColumn.z, thirdColumn.z}}; if (sampleRate>0) filter(rotationMatrix, false); // 3 - Computes and returns Euler angles (rx, ry, rz) from this matrix return rotationFromMatrix(rotationMatrix); } double[][] solveExtrinsicMatrix(List points2D) { // p ~= K · [R|t] · P // with P the (3D) corners of the physical board, p the (2D) // projected points onto the webcam image, K the intrinsic // matrix and R and t the rotation and translation we want to // compute. // // => We want to solve: (K^(-1) · p) X ([R|t] · P) = 0 float[][] projectedCorners = new float[4][3]; if(points2D.size() >= 4) for (int i=0; i<4; i++) { // TODO: // store in projectedCorners the result of (K^(-1) · p), for each // corner p found in the webcam image. // You can use PVector dot function for computing dot product between K^(-1) lines and p. //Do not forget to normalize the result PVector point =points2D.get(i); projectedCorners[i][0]=point.dot(invK_r1)/point.dot(invK_r3); projectedCorners[i][1]=point.dot(invK_r2)/point.dot(invK_r3); projectedCorners[i][2]=1; } // 'A' contains the cross-product (K^(-1) · p) X P float[][] A= new float[12][9]; for (int i=0; i<4; i++) { A[i*3][0]=0; A[i*3][1]=0; A[i*3][2]=0; // note that we take physicalCorners[0,1,*3*]: we drop the Z // coordinate and use the 2D homogenous coordinates of the physical // corners A[i*3][3]=-projectedCorners[i][2] * physicalCorners[i][0]; A[i*3][4]=-projectedCorners[i][2] * physicalCorners[i][1]; A[i*3][5]=-projectedCorners[i][2] * physicalCorners[i][3]; A[i*3][6]= projectedCorners[i][1] * physicalCorners[i][0]; A[i*3][7]= projectedCorners[i][1] * physicalCorners[i][1]; A[i*3][8]= projectedCorners[i][1] * physicalCorners[i][3]; A[i*3+1][0]= projectedCorners[i][2] * physicalCorners[i][0]; A[i*3+1][1]= projectedCorners[i][2] * physicalCorners[i][1]; A[i*3+1][2]= projectedCorners[i][2] * physicalCorners[i][3]; A[i*3+1][3]=0; A[i*3+1][4]=0; A[i*3+1][5]=0; A[i*3+1][6]=-projectedCorners[i][0] * physicalCorners[i][0]; A[i*3+1][7]=-projectedCorners[i][0] * physicalCorners[i][1]; A[i*3+1][8]=-projectedCorners[i][0] * physicalCorners[i][3]; A[i*3+2][0]=-projectedCorners[i][1] * physicalCorners[i][0]; A[i*3+2][1]=-projectedCorners[i][1] * physicalCorners[i][1]; A[i*3+2][2]=-projectedCorners[i][1] * physicalCorners[i][3]; A[i*3+2][3]= projectedCorners[i][0] * physicalCorners[i][0]; A[i*3+2][4]= projectedCorners[i][0] * physicalCorners[i][1]; A[i*3+2][5]= projectedCorners[i][0] * physicalCorners[i][3]; A[i*3+2][6]=0; A[i*3+2][7]=0; A[i*3+2][8]=0; } for (int i=0; i<12; i++) for (int j=0; j<9; j++) opencv_A.put(i, j, A[i][j]); Core.SVDecomp(opencv_A, w, u, vt); for (int i=0; i<9; i++) for (int j=0; j<9; j++) V[j][i]=vt.get(i, j)[0]; double[][] E = new double[3][3]; //E is the last column of V for (int i=0; i<9; i++) { E[i/3][i%3] = V[i][V.length-1] / V[8][V.length-1]; } return E; } PVector rotationFromMatrix(float[][] mat) { // Assuming rotation order is around x,y,z PVector rot = new PVector(); if (mat[1][0] > 0.998) { // singularity at north pole rot.z = 0; float delta = (float) Math.atan2(mat[0][1], mat[0][2]); rot.y = -(float) Math.PI/2; rot.x = -rot.z + delta; return rot; } if (mat[1][0] < -0.998) { // singularity at south pole rot.z = 0; float delta = (float) Math.atan2(mat[0][1], mat[0][2]); rot.y = (float) Math.PI/2; rot.x = rot.z + delta; return rot; } rot.y =-(float)Math.asin(mat[2][0]); rot.x = (float)Math.atan2(mat[2][1]/Math.cos(rot.y), mat[2][2]/Math.cos(rot.y)); rot.z = (float)Math.atan2(mat[1][0]/Math.cos(rot.y), mat[0][0]/Math.cos(rot.y)); return rot; } int filter(float m[][], boolean reset) { float[] q= new float[4]; float alpha, oneminusalpha, omega, cosomega, sinomega, s0, s1; mat2Quat(m, q); if (nomalizeQuaternion(q)<0) return -1; if (reset) { this.q[0] = q[0]; this.q[1] = q[1]; this.q[2] = q[2]; this.q[3] = q[3]; } else { alpha = this.alpha; oneminusalpha = 1.0 - alpha; // SLERP for orientation. cosomega = q[0]*this.q[0] + q[1]*this.q[1] + q[2]*this.q[2] + q[3]*this.q[3]; // cos of angle between vectors. if (cosomega < 0.0) { cosomega = -cosomega; q[0] = -q[0]; q[1] = -q[1]; q[2] = -q[2]; q[3] = -q[3]; } if (cosomega > 0.9995) { s0 = oneminusalpha; s1 = alpha; } else { omega = acos(cosomega); sinomega = sin(omega); s0 = sin(oneminusalpha * omega) / sinomega; s1 = sin(alpha * omega) / sinomega; } this.q[0] = q[0]*s1 + this.q[0]*s0; this.q[1] = q[1]*s1 + this.q[1]*s0; this.q[2] = q[2]*s1 + this.q[2]*s0; this.q[3] = q[3]*s1 + this.q[3]*s0; nomalizeQuaternion(this.q); } if (quat2Mat(this.q, m) < 0) return (-2); return (0); } int nomalizeQuaternion(float[] q) {// Normalise quaternion. float mag2 = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]; if (mag2==0) return (-1); float mag = sqrt(mag2); q[0] /= mag; q[1] /= mag; q[2] /= mag; q[3] /= mag; return (0); } int mat2Quat(float m[][], float q[]) { float t, s; t = m[0][0] + m[1][1] + m[2][2] + 1.0; if (t > 0.0001) { s = sqrt(t) * 2.0; q[0] = (m[1][2] - m[2][1]) / s; q[1] = (m[2][0] - m[0][2]) / s; q[2] = (m[0][1] - m[1][0]) / s; q[3] = 0.25 * s; } else { if (m[0][0] > m[1][1] && m[0][0] > m[2][2]) { // Column 0: s = sqrt(1.0 + m[0][0] - m[1][1] - m[2][2]) * 2.0; q[0] = 0.25 * s; q[1] = (m[0][1] + m[1][0] ) / s; q[2] = (m[2][0] + m[0][2] ) / s; q[3] = (m[1][2] - m[2][1] ) / s; } else if (m[1][1] > m[2][2]) { // Column 1: s = sqrt(1.0 + m[1][1] - m[0][0] - m[2][2]) * 2.0; q[0] = (m[0][1] + m[1][0] ) / s; q[1] = 0.25 * s; q[2] = (m[1][2] + m[2][1] ) / s; q[3] = (m[2][0] - m[0][2] ) / s; } else { // Column 2: s = sqrt(1.0 + m[2][2] - m[0][0] - m[1][1]) * 2.0; q[0] = (m[2][0] + m[0][2] ) / s; q[1] = (m[1][2] + m[2][1] ) / s; q[2] = 0.25 * s; q[3] = (m[0][1] - m[1][0] ) / s; } } return 0; } int quat2Mat( float q[], float m[][] ) { float x2, y2, z2; float xx, xy, xz; float yy, yz, zz; float wx, wy, wz; x2 = q[0] * 2.0; y2 = q[1] * 2.0; z2 = q[2] * 2.0; xx = q[0] * x2; xy = q[0] * y2; xz = q[0] * z2; yy = q[1] * y2; yz = q[1] * z2; zz = q[2] * z2; wx = q[3] * x2; wy = q[3] * y2; wz = q[3] * z2; m[0][0] = 1.0 - (yy + zz); m[1][1] = 1.0 - (xx + zz); m[2][2] = 1.0 - (xx + yy); m[1][0] = xy - wz; m[0][1] = xy + wz; m[2][0] = xz + wy; m[0][2] = xz - wy; m[2][1] = yz - wx; m[1][2] = yz + wx; return 0; } }