一、使用C++实现RANSAC拟合圆
/*************************************************Author: MaDate:2020-7-20Description:根据伪代码实现通用的RANSAC模板自定义圆模型,实现圆的拟合**************************************************/#include #include #include #include #include #include using namespace cv;#include #include "CSVOperator.h"using namespace std;//数据点类型typedef struct st_Point{ float x; float y;}st_Point;// 基于RANSAC算法的圆拟合// pstData: 指向存储数据的指针// dataCnt: 数据点个数// minCnt: 模型(圆)参数估计所需的数据点的个数// maxIterCnt: 最大迭代次数// maxErrorThreshold: 最大误差阈值// consensusCntThreshold: 模型一致性判断准则,内点所占百分比// modelMeanError: 模型误差// 返回值:返回0表示获取最优模型, 否则表示未获取最优模型int ransacLiner(st_Point* pstData, int dataCnt, int minCnt, double maxIterCnt, int consensusCntThreshold, double maxErrorThreshod, double& modelMeanError, double ¢erx, double ¢ery, double &Radius){ default_random_engine rng(time(0)); //随机数引擎 //default_random_engine rng(time(0)); uniform_int_distribution<unsigned> uniform(0, dataCnt - 1); //rng.seed(10); // 固定随机数种子 set<unsigned int> selectIndexs; // 选择的点的索引 vector selectPoints; // 选择的点 set<unsigned int> consensusIndexs; // 满足一致性的点的索引 //初始化参数 double center_x = 0; double center_y = 0; double radius = 0; modelMeanError = 0; int isNonFind = 1; unsigned int bestConsensusCnt = 0; // 满足一致性估计的点的个数 int iter = 0; while (iter < maxIterCnt)//小于最大迭代次数 { selectIndexs.clear(); selectPoints.clear(); // Step1: 随机选择minCnt个点,圆使用三个点 while (1) { unsigned int index = uniform(rng); selectIndexs.insert(index); if (selectIndexs.size() == minCnt) { break; } } // Step2: 进行模型参数估计,根据三个点求圆心和半径 set<unsigned int>::iterator selectIter = selectIndexs.begin(); while (selectIter != selectIndexs.end()) { unsigned int index = *selectIter; selectPoints.push_back(pstData[index]); selectIter++; } float x1 = (selectPoints[0]).x; float x2 = (selectPoints[1]).x; float x3 = (selectPoints[2]).x; float y1 = (selectPoints[0]).y; float y2 = (selectPoints[1]).y; float y3 = (selectPoints[2]).y; center_x= (x1*x1 + y1*y1)*(y2 - y3) + (x2*x2 + y2*y2)*(y3 - y1) + (x3*x3 + y3*y3)*(y1 - y2); center_x /= (2 * (x1*(y2 - y3) - y1*(x2 - x3) + x2*y3 - x3*y2)); center_y = (x1*x1 + y1*y1)*(x3 - x2) + (x2*x2 + y2*y2)*(x1 - x3) + (x3*x3 + y3*y3)*(x2 - x1); center_y /= (2 * (x1*(y2 - y3) - y1*(x2 - x3) + x2*y3 - x3*y2)); radius = sqrt((center_x - x1)*(center_x - x1) + (center_y - y1)*(center_y - y1)); // Step3: 进行模型评估: 点到圆的距离 int dataIter = 0; double meanError = 0; set<unsigned int> tmpConsensusIndexs; while (dataIter < dataCnt) { double x1 = pstData[dataIter].x; double y1 = pstData[dataIter].y; double x2 = center_x; double y2 = center_y; double absx1_x2 = (x1 - x2)*(x1 - x2); double absy1_y2 = (y1 - y2)*(y1 - y2); double disx1_x2 = sqrt(absx1_x2 + absy1_y2); double distance = abs(radius - sqrt(absx1_x2 + absy1_y2)); if (distance < maxErrorThreshod) { tmpConsensusIndexs.insert(dataIter); } meanError += distance; dataIter++; } // Step4: 判断一致性: 满足一致性集合的最小元素个数条件 + 至少比上一次的好 if (tmpConsensusIndexs.size() >= bestConsensusCnt && tmpConsensusIndexs.size() >= consensusCntThreshold) { bestConsensusCnt = consensusIndexs.size(); // 更新一致性索引集合元素个数 modelMeanError = meanError / dataCnt; consensusIndexs.clear(); consensusIndexs = tmpConsensusIndexs; // 更新一致性索引集合 centerx = center_x; centery = center_y; Radius = radius; isNonFind = 0; } iter++; } return isNonFind;}int main(){ st_Point dataPoints[60]; CCSVOperator CSVOperator; CSVOperator.LoadCSV("Circle.csv"); float x[60], y[60]; for (int i = 1; i < 61; i++)//245项 { if (CSVOperator.GetFloat(i, 1, dataPoints[i - 1].x)) { std::cout << "x[i]" << dataPoints[i - 1].x << '\n'; } if (CSVOperator.GetFloat(i, 2, dataPoints[i - 1].y)) { std::cout << "y[i]" << dataPoints[i - 1].y << '\n'; } } cout << sizeof(st_Point) << endl; cout << sizeof(float) << endl; cout << sizeof(dataPoints)/8<<endl; //collect time = 260ms // 参数不准确 clock_t start_time1 = clock(); while (1) { double centerx = 0; double centery = 0; double Radius = 0; double meanError = 0; int data_num = sizeof(dataPoints) / 8; if (!ransacLiner(dataPoints, 60, 3, 6, data_num / 2, 2, meanError, centerx, centery, Radius) && abs(Radius - 103) < 10) { cout << "centerx = " << centerx << endl; cout << "centery = " << centery << endl; cout << "Radius= " << Radius << endl; cout << "meanError = " << meanError << endl; break; } else { cout << "RANSAC Failed " << endl; } } clock_t end_time1 = clock(); double processTime1 = static_cast<double>(end_time1 - start_time1) / CLOCKS_PER_SEC * 1000; cout << "processTime=" << processTime1 << endl; system("pause"); return 0;}
二、C++产生随机数
C++11中,提供了一种新的获取随机数的方法。
以前获取伪随机数都是用的rand,想要获取两个数之间的伪随机数,方法如下:
//method 1 int min, max; //定义上下边界 max = 100; min = 1; int range = max - min; //获取中间的范围 srand(time(NULL)); for (int i = 0; i < 10; i++) { float randNum = rand() % range + min; cout << "num_rand" << i << ":" << randNum << endl; } //生成介于min和max之间的伪随机数
今天看《C++ Primer 5th Edition》里面介绍了使用default_random_engine来获取随机数,并且指出“C++程序不应该使用库函数rand,而应使用default_random_engine类和恰当的分布类对象。”看了一下,用这种新方法获取两个数之间的伪随机数的方法如下:
static default_random_engine rng; //rng.seed(20); // 固定随机数种子 //创建引擎 static int a =1, b=100; static uniform_int_distribution<unsigned> uniform(a, b); //创建取值范围 for (int i = 0; i < 10; i++) { float randNum = uniform(rng);; cout << "num_engine" << i << ":" << randNum << endl; } //生成介于min和max之间的伪随机数
从代码量来说,并没有减少,但是感觉更加直观了一些,毕竟可以直接设置上下边界,不用再通过取余之类的计算了。
有一个问题,就是多次调用同一对范围和引擎时,每次生成的数都是一样的。
使用rand()时,在函数前使用:
srand(time(NULL));
还可以设置种子:
关于设置种子,既可以在定义时设置种子,也可以创建完成后再设置种子,方法如下:
//上述随机数每次都是相同的,需要设置种子,可以设置时间为种子 default_random_engine u(time(0)); //default_random_engine u(10); //default_random_engine u; //u.seed(10); //创建引擎 uniform_int_distribution<unsigned> uniform1(min, max); //创建取值范围 for (int i = 0; i < 10; i++) { float randNum = uniform1(u);; cout << "num_time" << i << ":" << randNum << endl; }
然而time返回时间的单位是秒,所以如果是自动过程的一部分反复运行,比如用在循环中,那么因为间隔时间不够,所以设置的种子其实是一样的。
测试程序如下:
#include #include #include #include #include #include using namespace cv;#include #include "CSVOperator.h"//数据点类型using namespace std;int main(){ clock_t start_time1 = clock(); //method 1 int min, max; //定义上下边界 max = 100; min = 1; int range = max - min; //获取中间的范围 srand(time(NULL)); for (int i = 0; i < 10; i++) { float randNum = rand() % range + min; cout << "num_rand" << i << ":" << randNum << endl; } //生成介于min和max之间的伪随机数 //method 2 //static default_random_engine rng; 创建引擎 //static uniform_int_distribution uniform(min, max); 创建取值范围 static default_random_engine rng; //rng.seed(20); // 固定随机数种子 //创建引擎 static int a =1, b=100; static uniform_int_distribution<unsigned> uniform(a, b); //创建取值范围 for (int i = 0; i < 10; i++) { float randNum = uniform(rng);; cout << "num_engine" << i << ":" << randNum << endl; } //生成介于min和max之间的伪随机数 //上述随机数每次都是相同的,需要设置种子,可以设置时间为种子 default_random_engine u(time(0)); //default_random_engine u(10); //default_random_engine u; //u.seed(10); //创建引擎 uniform_int_distribution<unsigned> uniform1(min, max); //创建取值范围 for (int i = 0; i < 10; i++) { float randNum = uniform1(u);; cout << "num_time" << i << ":" << randNum << endl; } clock_t end_time1 = clock(); double processTime1 = static_cast<double>(end_time1 - start_time1) / CLOCKS_PER_SEC * 1000; cout << "processTime" << processTime1 << endl; system("pause"); return 0;}
参考文献:
1、《C++ Primer 5th Edition》 17.4 随机数
2、https://blog.csdn.net/tsbyj/article/details/46994851
3、RANSAC直线拟合(五十)
推荐:机器人实时纠偏系统(一)
机器人实时纠偏(二)OpenCV+VS开发环境配置(三)结构光视觉的焊接机器人纠偏(四)结构光视觉的机器人焊接(五)结构光视觉的机器人焊接(六)机器人初始点导引(七)MATLAB标定相机参数(八)机器人的手眼标定(九)机器人坐标获取(十)机器人调试(十一)TCP/IP客户端API编程(十二)结构光传感器上位机界面多线程编程(十三)TCP&UDP(十四)C/C++ Programing(十五)机器人扫描与跟踪调试(十六)结构光传感器库函数(十七)结构光传感器编程(十八)C/C++ Programing(十九)C/C++ Programing(二十)结构光传感器编程(二十一)DX200操作要领(二十二)DX200操作要领(二十三)工装轴协调(二十四)无夹具协调(二十五)图像处理调试(二十六)STM32MODBUS_CRC编程(二十七)在C++中调用Matlab函数(二十八)
机器人手眼标定MATLAB及C++实现
机器人位姿运算及Eigen的使用(三十)
OpenCV与Eigen矩阵运算(三十一)
VS中数据读写及OpenCV拟合(三十二)
VS2013配置OpenGL库(三十三)
曲线拟合/插值(三十四)
曲线拟合绘制滤波及机器人平移(三十五)
DX200操作要领—示教1(三十六)
直接打开与平移变换(三十七)PAM与镜像平移变换(三十八)
修改与编辑程序(三十九)
YRC1000 宏程序命令(四十)
程序编辑与试运行(四十一)
程序编辑与再现(四十二)
再现(四十三)
程序管理(四十四)
便捷功能(四十五)
便捷功能(四十六)
椭圆拟合(四十七)
RANSAC直线拟合(四十八)
读写CSV文件类(四十九)
RANSAC直线拟合(五十)
法向量、旋转矩阵计算(五十一)
机器人手眼标定与变量设置调试(五十二)
20200715调试记录(五十三)
20200717调试记录(五十四)