CV学习日志:CV开发之便携仿真器

Posted dzybk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CV学习日志:CV开发之便携仿真器相关的知识,希望对你有一定的参考价值。

         视觉算法开发过程中,如何快速地验证算法是一个头疼的问题。

         通常有两种方式,或是下载公开数据来验证,或是通过仿真环境采集数据来验证,然而这两种方式都不便携,即都需要大量的准备工作和一定的存储要求。

         本文介绍一个简单袖珍的仿真器,可快速获取视觉算法所需要的验证数据,用于视觉标定和视觉定位等算法。

         此仿真器主要是由一系列世界坐标点和一个运动相机组成,运动相机从世界坐标系的原点运动到不能再观察到足够多的世界坐标点时或已进行足够次数的观察后退出,每运动一个步长进行一次观察,运动信息参见后文或源码,观察时获取哪些数据参见源码。

         关于仿真器的大致信息如下:

         (1)关于坐标系:世界系采用Z轴向下的右手坐标系,相机系亦同。

         (2)关于世界目标:在世界中创建了100*100个世界坐标,所有Z坐标是-1~-99的随机分布,设I从0到100且J从0到100,X坐标的变化规则是10乘以J再加一个0~9的随机数,Y坐标的变化规则是10乘以I再加一个0~9的随机数。

         (3)关于观察位置变化:相机每次成像时的Z坐标是-101~-150的一个随机数,X坐标等于上一次的X坐标加一个0~9的随机数,Y坐标的变化规律也一样,初始X和Y都为0。也可设置观察位置无变化,即相机始终位于初次观察位置。

         (4)关于观察姿态变化:相机每次成像时的Z朝向是-5~5的一个随机角度,Y朝向和X朝向变化规律也一样。也可设置观察朝向始终为0或只有XYZ中的两个朝向或只有XYZ中的一个朝向。

         (5)关于运动退出条件:理论上只图像足够大,100*100个世界坐标对应100*100个像素坐标,但若限制图像尺寸后则每次成像时都有部分像素坐标不可用,这里限制当可用的像素坐标少于指定值(默认128)时则相机退出运动。退出的另一条件是已进行指定次数(默认INT_MAX)的观察。

         (6)关于可视化:启动可视化后,按空格可逐次可视化每次观察,当到达最后一次观察后则返回第一次观察。

 

         以下是详细代码,依赖于C++14、OpenCV4.x和Spdlog。

技术图片
  1 #include <opencv2/opencv.hpp>
  2 #include <opencv2/viz.hpp>
  3 #include <ceres/ceres.h>
  4 #include <spdlog/spdlog.h>
  5 using namespace std;
  6 using namespace cv;
  7 
  8 class MotionSim
  9 {
 10 public:
 11     static void TestMe(int argc, char** argv)
 12     {
 13         MotionSim motionSim;
 14         motionSim.visMotion();
 15     }
 16 
 17 public:
 18     struct MotionNode
 19     {
 20         Mat_<double> r = Mat_<double>(3, 1);
 21         Mat_<double> t = Mat_<double>(3, 1);
 22         Mat_<Vec3d> point3D;
 23         Mat_<Vec2d> point2D;
 24         Mat_<int> point3DIds;
 25         Mat_<double> K;
 26         Mat_<double> D;
 27         Mat_<double> R = Mat_<double>(3, 3);
 28         Mat_<double> T = Mat_<double>(3, 4);
 29         Mat_<double> degree = Mat_<double>(3, 1);
 30         string print(string savePath = "")
 31         {
 32             string str;
 33             str += fmt::format("K: {}
", cvarr2str(K));
 34             str += fmt::format("D: {}
", cvarr2str(D));
 35             str += fmt::format("r: {}
", cvarr2str(r));
 36             str += fmt::format("t: {}
", cvarr2str(t));
 37             str += fmt::format("R: {}
", cvarr2str(R));
 38             str += fmt::format("T: {}
", cvarr2str(T));
 39             str += fmt::format("degree: {}
", cvarr2str(degree));
 40             if (savePath.empty() == false) { FILE* out = fopen(savePath.c_str(), "w"); fprintf(out, str.c_str()); fclose(out); }
 41             return str;
 42         }
 43     };
 44     static string cvarr2str(InputArray v)
 45     {
 46         Ptr<Formatted> fmtd = cv::format(v, Formatter::FMT_DEFAULT);
 47         string dst; fmtd->reset();
 48         for (const char* str = fmtd->next(); str; str = fmtd->next()) dst += string(str);
 49         return dst;
 50     }
 51     static void euler2matrix(double degree[3], double matrix[9])
 52     {
 53         double deg2rad = 3.14159265358979323846 * 0.0055555555555555556;
 54         double radian[3] = { degree[0] * deg2rad,degree[1] * deg2rad, degree[2] * deg2rad };
 55         double sinR = sin(radian[0]);
 56         double sinP = sin(radian[1]);
 57         double sinY = sin(radian[2]);
 58         double cosR = cos(radian[0]);
 59         double cosP = cos(radian[1]);
 60         double cosY = cos(radian[2]);
 61 
 62         //RPY indicates: first Yaw aroundZ, second Pitch aroundY, third Roll aroundX
 63         matrix[0] = cosY * cosP; matrix[1] = cosY * sinP * sinR - sinY * cosR; matrix[2] = cosY * sinP * cosR + sinY * sinR;
 64         matrix[3] = sinY * cosP; matrix[4] = sinY * sinP * sinR + cosY * cosR; matrix[5] = sinY * sinP * cosR - cosY * sinR;
 65         matrix[6] = -sinP;       matrix[7] = cosP * sinR;                      matrix[8] = cosP * cosR;
 66     };
 67 
 68 private:
 69     const int nHorPoint3D = 100;
 70     const int nVerPoint3D = 100;
 71     const double varPoint3DXY = 10.;
 72     const double minPoint3DZ = 1.;
 73     const double maxPoint3DZ = 99.;
 74     const double minCamZ = 101.;
 75     const double maxCamZ = 150.;
 76     const double varCamDegree = 5.;
 77     Mat_<Vec3d> allPoint3D = Mat_<Vec3d>(nVerPoint3D * nHorPoint3D, 1);
 78     Mat_<double> allPoint3DZ = Mat_<double>(nVerPoint3D * nHorPoint3D, 1);
 79     Mat_<double> K = Mat_<double>(3, 3, 0.);
 80     Mat_<double> D = Mat_<double>(8, 1, 0.);
 81 
 82 public:
 83     vector<MotionNode> motionNodes;//World Information: RightX, FrontY, DownZ
 84     MotionSim(int imaSide = 512, int leastNodeTheRestart = 32, int mostNodeThenExit = INT_MAX, int leastFeaturesThenExit = 128, 
 85         int rotAxis = 1 + 2 + 4, bool isTrans = true, bool enableRandomSeek = true, bool enableVerbose = false)
 86     {
 87         if (enableRandomSeek) cv::setRNGSeed(clock());
 88         while (motionNodes.size() < leastNodeTheRestart)
 89         {
 90             //1.GetAllPoint3D
 91             cv::randu(allPoint3DZ, -maxPoint3DZ, -minPoint3DZ);//DownZ
 92             for (int i = 0, k = 0; i < nVerPoint3D; ++i)
 93                 for (int j = 0; j < nHorPoint3D; ++j, ++k)
 94                     allPoint3D(k) = Vec3d((j + cv::randu<double>()) * varPoint3DXY, (i + cv::randu<double>()) * varPoint3DXY, allPoint3DZ(i, j));
 95 
 96             //2.GetCamParams
 97             cv::randu(K, imaSide / 2 - 10., imaSide / 2 + 10.); K(0, 1) = K(1, 0) = K(2, 0) = K(2, 1) = 0; K(2, 2) = 1;
 98             cv::randu(D, -1.0, 1.0);
 99 
100             //3.GetAllMotionNode
101             motionNodes.clear();
102             for (int64 k = 0; ; ++k)
103             {
104                 //3.1 JoinCamParams
105                 MotionNode node;
106                 node.K = K.clone();
107                 node.D = D.clone();
108 
109                 //3.2 GetCamTrans
110                 if (k == 0) node.t(0) = node.t(1) = 0;
111                 else
112                 {
113                     node.t(0) = motionNodes[k - 1].t(0) + cv::randu<double>() * varPoint3DXY;
114                     node.t(1) = motionNodes[k - 1].t(1) + cv::randu<double>() * varPoint3DXY;
115                 }
116                 node.t(2) = minCamZ + cv::randu<double>() * (maxCamZ - minCamZ);
117                 node.t(2) = -node.t(2);//DownZ
118                 if (isTrans == false && k != 0) { node.t(0) = motionNodes[0].t(0); node.t(1) = motionNodes[0].t(1); node.t(2) = motionNodes[0].t(2); }
119 
120                 //3.3 GetCamRot
121                 node.degree = 0.;
122                 if (rotAxis & 1) node.degree(0) = cv::randu<double>() * varCamDegree;
123                 if (rotAxis & 2) node.degree(1) = cv::randu<double>() * varCamDegree;
124                 if (rotAxis & 4) node.degree(2) = cv::randu<double>() * varCamDegree;
125                 this->euler2matrix(node.degree.ptr<double>(), node.R.ptr<double>());
126                 cv::Rodrigues(node.R, node.r);
127                 cv::hconcat(node.R, node.t, node.T);
128 
129                 //3.4 GetPoint3DAndPoint2D
130                 Mat_<Vec2d> allPoint2D;
131                 cv::projectPoints(allPoint3D, -node.r, -node.R.t() * node.t, node.K, node.D, allPoint2D);
132                 for (int k = 0; k < allPoint2D.total(); ++k)
133                     if (allPoint2D(k)[0] > 0 && allPoint2D(k)[0] < imaSide && allPoint2D(k)[1] > 0 && allPoint2D(k)[1] < imaSide)
134                     {
135                         node.point2D.push_back(allPoint2D(k));
136                         node.point3D.push_back(allPoint3D(k));
137                         node.point3DIds.push_back(k);
138                     }
139 
140                 //3.5 PrintDetails
141                 motionNodes.push_back(node);
142                 if (enableVerbose)
143                 {
144                     cout << endl << node.print();
145                     cout << fmt::format("view={}   features={}
", k, node.point2D.rows);
146                     double minV = 0, maxV = 0;//Distortion makes some minV next to maxV
147                     int minId = 0, maxId = 0;
148                     cv::minMaxIdx(allPoint2D.reshape(1, int(allPoint2D.total()) * allPoint2D.channels()), &minV, &maxV, &minId, &maxId);
149                     cout << fmt::format("minInfo:({}, {})", minId, minV) << allPoint3D(minId / 2) << allPoint2D(minId / 2) << endl;
150                     cout << fmt::format("maxInfo:({}, {})", maxId, maxV) << allPoint3D(maxId / 2) << allPoint2D(maxId / 2) << endl;
151                     std::getchar();
152                 }
153                 if (node.point2D.rows < leastFeaturesThenExit || motionNodes.size() > mostNodeThenExit) break;
154             }
155         }
156     }
157     void visMotion()
158     {
159         //1.CreateWidgets
160         Size2d validSize(nHorPoint3D * varPoint3DXY, nVerPoint3D * varPoint3DXY);
161         Mat_<cv::Affine3d> camPoses(int(motionNodes.size()), 1); for (int k = 0; k < camPoses.rows; ++k) camPoses(k) = cv::Affine3d(motionNodes[k].T);
162         viz::WText worldInfo(fmt::format("nMotionNode: {}
K: {}
D: {}", motionNodes.size(), cvarr2str(K), cvarr2str(D)), Point(10, 240), 10);
163         viz::WCoordinateSystem worldCSys(1000);
164         viz::WPlane worldGround(Point3d(validSize.width / 2, validSize.height / 2, 0), Vec3d(0, 0, 1), Vec3d(0, 1, 0), validSize);
165         viz::WCloud worldPoints(allPoint3D, Mat_<Vec3b>(allPoint3D.size(), Vec3b(0, 255, 0)));
166         viz::WTrajectory camTraj1(camPoses, viz::WTrajectory::FRAMES, 8);
167         viz::WTrajectorySpheres camTraj2(camPoses, 100, 2);
168         viz::WTrajectoryFrustums camTraj3(camPoses, Matx33d(K), 4., viz::Color::yellow());
169         worldCSys.setRenderingProperty(viz::OPACITY, 0.1);
170         worldGround.setRenderingProperty(viz::OPACITY, 0.1);
171         camTraj2.setRenderingProperty(viz::OPACITY, 0.6);
172 
173         //2.ShowWidgets
174         static viz::Viz3d viz3d(__FUNCTION__);
175         viz3d.showWidget("worldInfo", worldInfo);
176         viz3d.showWidget("worldCSys", worldCSys);
177         viz3d.showWidget("worldGround", worldGround);
178         viz3d.showWidget("worldPoints", worldPoints);
179         viz3d.showWidget("camTraj1", camTraj1);
180         viz3d.showWidget("camTraj2", camTraj2);
181         viz3d.showWidget("camTraj3", camTraj3);
182 
183         //3.UpdateWidghts
184         static const vector<MotionNode>& nodes = motionNodes;
185         viz3d.registerKeyboardCallback([](const viz::KeyboardEvent& keyboarEvent, void* pVizBorad)->void
186             {
187                 if (keyboarEvent.action != viz::KeyboardEvent::KEY_DOWN) return;
188                 static int ind = 0;
189                 if (keyboarEvent.code ==  )
190                 {
191                     size_t num = nodes.size();
192                     if (ind != 0)
193                     {
194                         for (int k = 0; k < nodes[ind % num == 0 ? num - 1 : ind % num - 1].point3D.rows; ++k) viz3d.removeWidget("active" + std::to_string(k));
195                         viz3d.removeWidget("nodeInfo");
196                         viz3d.removeWidget("camSolid");
197                     }
198                     for (int k = 0; k < nodes[ind % num].point3D.rows; ++k) viz3d.showWidget("active" + std::to_string(k), viz::WSphere(nodes[ind % num].point3D(k), 5, 10));
199                     viz3d.showWidget("nodeInfo", viz::WText(fmt::format("CurrentNode: {}
ValidPoit3D: {}", ind % num, nodes[ind % num].point3D.rows), Point(10, 10), 10));
200                     viz3d.showWidget("camSolid", viz::WCameraPosition(Matx33d(nodes[ind % num].K), 10, viz::Color::yellow()), cv::Affine3d(nodes[ind % num].T));
201                     ++ind;
202                 }
203             }, 0);
204         viz3d.spin();
205     }
206 };
207 
208 class OptimizeRt : public ceres::CostFunction
209 {
210 public://Test
211     using MotionNode = MotionSim::MotionNode;
212     static void TestMe(int argc, char** argv)
213     {
214         MotionSim motionSim;
215         for (size_t k = 0; k < motionSim.motionNodes.size(); ++k)
216         {
217 
218         }
219     }
220 
221 public://Core
222     const MotionNode& motionNode;
223     OptimizeRt(MotionNode motionNode0) : motionNode(motionNode0)
224     {
225         this->set_num_residuals(motionNode.point3D.rows * 2);
226         this->mutable_parameter_block_sizes()->push_back(6);//also could use two groups of params: r and t
227     }
228     bool Evaluate(double const* const* params, double* residuals, double** jacobians) const
229     {
230         //1.FormatInput
231         Matx31d r(params[0]);
232         Matx31d t(params[0] + 3);
233         Mat_<Vec2d> errVector(motionNode.point2D.rows, 1, (Vec2d*)(residuals));
234         Mat_<double> dpdrt;
235         if (jacobians && jacobians[0]) dpdrt = Mat_<double>(motionNode.point2D.rows * 2, 6, jacobians[0]);
236 
237         ////2.CalcErrorAndJacobian
238         //Mat_<double> dpdKDT;
239         //Mat_<Matx21d> pDs2(pDs.size());
240         //cv::projectPoints(PWs, r, t, K, D, pDs2, dpdKDT);
241         //errVector = pDs2 - pDs;
242         //if (jacobians && jacobians[0]) dpdKDT.colRange(0, 6).copyTo(dpdrt);
243 
244         return true;
245     }
246 };
247 
248 int main(int argc, char** argv) { MotionSim::TestMe(argc, argv); return 0; }
View Code

技术图片

技术图片

技术图片

 

 

 

 

以上是关于CV学习日志:CV开发之便携仿真器的主要内容,如果未能解决你的问题,请参考以下文章

巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进

巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进

巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进

巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进

巧用Vscode编辑器,快速编辑代码,教你一键写完一段代码,向合格的cv工程师前进

计算机视觉(CV)sklearn之分类算法与手写数字识别