Opencv--minEnclosingCircle源码--求最小包围圆的算法
来源:互联网 发布:单片机gsm模块有哪些 编辑:程序博客网 时间:2024/05/22 18:35
在Opencv中,求最小包围圆的函数:
他的方法是什么呢?怎么找出这个最小包围圆的呢?
为了方便理解源码,我新建了一个工程,把相关的源文件复制过来,再输入一张测试图,让代码跑起来看中间过程。
工程文件包括:
其中 my_minEnclosingCircle是main函数入口,其他文件从C:\...\opencv\sources\modules\... 路径下搜索,然后复制到该工程文件夹内。这样在代码中使用“转到定义”的时候就可以查看相关源码了,还可以用不同的测试图去测试。
main函数:
#include <opencv2/opencv.hpp>using namespace cv;using namespace std;int main(){Mat gray = imread("3.jpg",0);Mat src;threshold(gray, src, 200, 255, THRESH_BINARY);imshow("src", src);vector< vector<Point> > contours;vector<Vec4i> hiearachy;Mat src_cp = src.clone();findContours(src_cp, contours, hiearachy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);Point2f center;float radius;minEnclosingCircle(contours[0], center, radius);Mat dst(src.size(), CV_8UC3, Scalar::all(0));Point center1;center1.x = cvRound (center.x);center1.y = cvRound(center.y);int radius1 = cvRound(radius);circle(dst, center1, radius1, Scalar(0,0,255), 2, 8);dst.setTo(Scalar(255,255,255), src);imwrite("dst.jpg", dst);waitKey(0);return 0;}
测试图(左)、自己做一些标记(中)、最小包围圆(右)
minEnclosingCircle 函数定义:
void cv::minEnclosingCircle( InputArray _points,Point2f& center, float& radius ){Mat points = _points.getMat();CV_Assert(points.checkVector(2) >= 0 && (points.depth() == CV_32F || points.depth() == CV_32S));CvMat _cpoints = points;cvMinEnclosingCircle( &_cpoints, (CvPoint2D32f*)¢er, &radius );}
c++的接口,然后转到c函数中去。cvMinEnclosingCircle函数定义:
CV_IMPL intcvMinEnclosingCircle( const void* array, CvPoint2D32f * _center, float *_radius ){ const int max_iters = 100; const float eps = FLT_EPSILON*2; CvPoint2D32f center = { 0, 0 }; float radius = 0; int result = 0; if( _center ) _center->x = _center->y = 0.f; if( _radius ) *_radius = 0; CvSeqReader reader; int k, count; CvPoint2D32f pts[8]; CvContour contour_header; CvSeqBlock block; CvSeq* sequence = 0; int is_float; if( !_center || !_radius ) CV_Error( CV_StsNullPtr, "Null center or radius pointers" ); if( CV_IS_SEQ(array) ) { sequence = (CvSeq*)array; if( !CV_IS_SEQ_POINT_SET( sequence )) CV_Error( CV_StsBadArg, "The passed sequence is not a valid contour" ); } else { sequence = cvPointSeqFromMat( CV_SEQ_KIND_GENERIC, array, &contour_header, &block ); } if( sequence->total <= 0 ) CV_Error( CV_StsBadSize, "" ); cvStartReadSeq( sequence, &reader, 0 ); count = sequence->total; is_float = CV_SEQ_ELTYPE(sequence) == CV_32FC2; if( !is_float ) { CvPoint *pt_left, *pt_right, *pt_top, *pt_bottom; CvPoint pt; pt_left = pt_right = pt_top = pt_bottom = (CvPoint *)(reader.ptr); CV_READ_SEQ_ELEM( pt, reader ); for(int i = 1; i < count; i++ ) { CvPoint* pt_ptr = (CvPoint*)reader.ptr; CV_READ_SEQ_ELEM( pt, reader ); if( pt.x < pt_left->x ) pt_left = pt_ptr; if( pt.x > pt_right->x ) pt_right = pt_ptr; if( pt.y < pt_top->y ) pt_top = pt_ptr; if( pt.y > pt_bottom->y ) pt_bottom = pt_ptr; } pts[0] = cvPointTo32f( *pt_left ); pts[1] = cvPointTo32f( *pt_right ); pts[2] = cvPointTo32f( *pt_top ); pts[3] = cvPointTo32f( *pt_bottom ); } else { CvPoint2D32f *pt_left, *pt_right, *pt_top, *pt_bottom; CvPoint2D32f pt; pt_left = pt_right = pt_top = pt_bottom = (CvPoint2D32f *) (reader.ptr); CV_READ_SEQ_ELEM( pt, reader ); for(int i = 1; i < count; i++ ) { CvPoint2D32f* pt_ptr = (CvPoint2D32f*)reader.ptr; CV_READ_SEQ_ELEM( pt, reader ); if( pt.x < pt_left->x ) pt_left = pt_ptr; if( pt.x > pt_right->x ) pt_right = pt_ptr; if( pt.y < pt_top->y ) pt_top = pt_ptr; if( pt.y > pt_bottom->y ) pt_bottom = pt_ptr; } pts[0] = *pt_left; pts[1] = *pt_right; pts[2] = *pt_top; pts[3] = *pt_bottom; } for( k = 0; k < max_iters; k++ ) { double min_delta = 0, delta; CvPoint2D32f ptfl, farAway = { 0, 0}; /*only for first iteration because the alg is repared at the loop's foot*/ if(k==0) icvFindEnslosingCicle4pts_32f( pts, ¢er, &radius ); cvStartReadSeq( sequence, &reader, 0 ); for(int i = 0; i < count; i++ ) { if( !is_float ) { ptfl.x = (float)((CvPoint*)reader.ptr)->x; ptfl.y = (float)((CvPoint*)reader.ptr)->y; } else { ptfl = *(CvPoint2D32f*)reader.ptr; } CV_NEXT_SEQ_ELEM( sequence->elem_size, reader ); delta = icvIsPtInCircle( ptfl, center, radius ); if( delta < min_delta ) { min_delta = delta; farAway = ptfl; } } result = min_delta >= 0; if( result ) break; CvPoint2D32f ptsCopy[4]; /* find good replacement partner for the point which is at most far away, starting with the one that lays in the actual circle (i=3) */ for(int i = 3; i >=0; i-- ) { for(int j = 0; j < 4; j++ ) { ptsCopy[j]=(i != j)? pts[j]: farAway; } icvFindEnslosingCicle4pts_32f(ptsCopy, ¢er, &radius ); if( icvIsPtInCircle( pts[i], center, radius )>=0){ // replaced one again in the new circle? pts[i] = farAway; break; } } } if( !result ) { cvStartReadSeq( sequence, &reader, 0 ); radius = 0.f; for(int i = 0; i < count; i++ ) { CvPoint2D32f ptfl; float t, dx, dy; if( !is_float ) { ptfl.x = (float)((CvPoint*)reader.ptr)->x; ptfl.y = (float)((CvPoint*)reader.ptr)->y; } else { ptfl = *(CvPoint2D32f*)reader.ptr; } CV_NEXT_SEQ_ELEM( sequence->elem_size, reader ); dx = center.x - ptfl.x; dy = center.y - ptfl.y; t = dx*dx + dy*dy; radius = MAX(radius,t); } radius = (float)(sqrt(radius)*(1 + eps)); result = 1; } *_center = center; *_radius = radius; return result;}
输入一个点集,输出圆心和半径。总结算法的几个步骤:
- 1.遍历所有点,找出最左边、最右边、最上边、最下边的四个点,分别用A、B、C、D来表示。
- 2.求出包围这四个点的最小圆C1的圆心和半径;
- 3.第一次迭代(k=0)。遍历所有点,检查是否存在点出界,即不在圆C1界内也不在边界上。如果没有出界点,则求出的圆就是最终所求。如果有出界点,转到第四步。
- 4.假设出界点中距离圆C1的圆心最远的点为E,则依次尝试以下四种组合:(1)A/B/C/E (2)A/B/D/E (3)A/C/D/E (4)B/C/D/E。求出该组合中四个点的最小包围圆。假设组合(1)A、B、C、E的最小包围圆是C2,然后检测剩下的点D在不在C2内,比如说不在,那就算组合(2),算出A、B、D、E四个点的最小包围圆C3,检测剩下的点C,发现在圆C3内,那么记录下点E和圆C3的圆心、半径。
- 5.第二次迭代(k=1)。遍历所有点,检查是否存在出界点,即不在圆C3界内也不在边界上。如果没有出界点,则求出的圆C3就是最终所求。如果有出界点,则转到下一步。
- 6.假设出界点中距离圆C3的圆心最远的点为F,则依次尝试以下四种组合:(1)A/B/D/F (2)A/B/E/F (3)A/D/E/F (4)B/D/E/F (这里已经没有C什么事了,它已经在上一轮被淘汰了)。依次处理这四个组合,求出该组合中四个点的最小包围圆。假设组合(1)求出圆C4,检测点E在圆C4内,则么记录下点F和圆C4的圆心、半径。
- 7.第三次迭代(k=2)。接下来重复第5步、第6步,直到发现遍历完所有点结果都在最新求出的圆界内,则该圆就是最终所求的圆,退出迭代。
在cvMinEnclosingCircle 函数中,pts[0]、pts[1] 、pts[2] 、pts[3] 分别用来表示最左边、最右边、最上边、最下边的四个点。
最大迭代次数设置为100. for( k = 0; k < max_iters; k++ )部分一个大括号中完成一次迭代。
icvFindEnslosingCicle4pts_32f( pts, ¢er, &radius ); 该函数是用来求四个点的最小包围圆。从数学原理上,三个不在一条直线上的点确定一个圆。那四个点的最小包围圆怎么求呢?是否是唯一的呢?参考网上的回答:
根据代码中的办法,首先找到距离最远的两个点,比如B和D,保存在idxs[0]、idxs[1]中,然后求出这两个点的中心P,距离的一半为半径,画出圆C11。然后判断剩下的两个点A和C是否过界,如果不过界则圆C11为最终所求的圆,如果有过界的点,则取四个点中的三个画圆,判断第四个点是否在圆内。四种组合中,如果画出了不止一个圆,则比较半径,取半径最小的那个圆作为最终所求的圆。
贴出icvFindEnslosingCicle4pts_32f( pts, ¢er, &radius ) 函数代码:
static inticvFindEnslosingCicle4pts_32f( CvPoint2D32f * pts, CvPoint2D32f * _center, float *_radius ){ //在理解代码的过程,假设 pts[0]和pts[2]之间的距离最远,即左边和上面的两个点距离最远,右边点的距离比较远。int shuffles[4][4] = { {0, 1, 2, 3}, {0, 1, 3, 2}, {2, 3, 0, 1}, {2, 3, 1, 0} }; int idxs[4] = { 0, 1, 2, 3 }; //idxs[4]里面装了四个点的序号,初值给一任意值。 int i, j, k = 1, mi = 0; float max_dist = 0; CvPoint2D32f center; //初始值为pts[0], 后面计算 最远的两个点的中点,保存在这。 CvPoint2D32f min_center; //初始值为pts[0], float radius, min_radius = FLT_MAX; CvPoint2D32f res_pts[4]; center = min_center = pts[0]; radius = 1.f;//两个for循环为了找到距离最远的两个点,更新idxs[0]、idxs[1]的值为这两个点的序号。 for( i = 0; i < 4; i++ ) for( j = i + 1; j < 4; j++ ) { float dist = icvDistanceL2_32f( pts[i], pts[j] ); if( max_dist < dist ) { max_dist = dist; idxs[0] = i; idxs[1] = j; } } if( max_dist == 0 ) goto function_exit;// idxs[0]、idxs[1]已经装了最远的两个点的序号;排除这两个序号,把下一个序号装在idxs[2]里面;排除以上三个序号,把下一个序号装在idxs[3]里面。 k = 2; for( i = 0; i < 4; i++ ) { for( j = 0; j < k; j++ ) if( i == idxs[j] ) break; if( j == k ) idxs[k++] = i; } center = cvPoint2D32f( (pts[idxs[0]].x + pts[idxs[1]].x)*0.5f, (pts[idxs[0]].y + pts[idxs[1]].y)*0.5f ); radius = (float)(icvDistanceL2_32f( pts[idxs[0]], center )*1.03); //最远距离的一半,再乘以1.03. if( radius < 1.f ) radius = 1.f; if( icvIsPtInCircle( pts[idxs[2]], center, radius ) >= 0 && // if( pts[idxs[2]]与center这两个点之间的距离小于radius ) icvIsPtInCircle( pts[idxs[3]], center, radius ) >= 0 ) // if( pts[idxs[3]]与center这两个点之间的距离小于radius ) { k = 2; //rand()%2+2; } else { mi = -1; for( i = 0; i < 4; i++ ) { if( icvFindCircle( pts[shuffles[i][0]], pts[shuffles[i][1]], pts[shuffles[i][2]], ¢er, &radius ) >= 0 ) { radius *= 1.03f; if( radius < 2.f ) radius = 2.f; if( icvIsPtInCircle( pts[shuffles[i][3]], center, radius ) >= 0 && min_radius > radius ) { min_radius = radius; min_center = center; mi = i; } } } assert( mi >= 0 ); if( mi < 0 ) mi = 0; k = 3; center = min_center; radius = min_radius; for( i = 0; i < 4; i++ ) idxs[i] = shuffles[mi][i]; } function_exit: *_center = center; *_radius = radius; /* reorder output points */ for( i = 0; i < 4; i++ ) res_pts[i] = pts[idxs[i]]; for( i = 0; i < 4; i++ ) { pts[i] = res_pts[i]; assert( icvIsPtInCircle( pts[i], center, radius ) >= 0 ); } return k;}
怎么判断点是否在圆内或者圆上?
答:计算该点与圆心之间的距离,记为d。 圆的半径记为r。 则计算 r^2 - d^2 。这个值如果>0, 则在圆内,如果=0,则在圆上,如果<0,则在圆外。
CV_INLINE double icvIsPtInCircle( CvPoint2D32f pt, CvPoint2D32f center, float radius ){ double dx = pt.x - center.x; double dy = pt.y - center.y; return (double)radius*radius - dx*dx - dy*dy;}
三点确定一个圆,怎么用代码表示呢?
答:我没理清楚公式,先放在这里。
static CvStatusicvFindCircle( CvPoint2D32f pt0, CvPoint2D32f pt1, CvPoint2D32f pt2, CvPoint2D32f * center, float *radius ){ double x1 = (pt0.x + pt1.x) * 0.5; double dy1 = pt0.x - pt1.x; double x2 = (pt1.x + pt2.x) * 0.5; double dy2 = pt1.x - pt2.x; double y1 = (pt0.y + pt1.y) * 0.5; double dx1 = pt1.y - pt0.y; double y2 = (pt1.y + pt2.y) * 0.5; double dx2 = pt2.y - pt1.y; double t = 0; CvStatus result = CV_OK; if( icvIntersectLines( x1, dx1, y1, dy1, x2, dx2, y2, dy2, &t ) >= 0 ) { center->x = (float) (x2 + dx2 * t); center->y = (float) (y2 + dy2 * t); *radius = (float) icvDistanceL2_32f( *center, pt0 ); } else { center->x = center->y = 0.f; radius = 0; result = CV_NOTDEFINED_ERR; } return result;}
其中,icvIntersectLines()函数:
inticvIntersectLines( double x1, double dx1, double y1, double dy1, double x2, double dx2, double y2, double dy2, double *t2 ){ double d = dx1 * dy2 - dx2 * dy1; int result = -1; if( d != 0 ) { *t2 = ((x2 - x1) * dy1 - (y2 - y1) * dx1) / d; result = 0; } return result;}
- Opencv--minEnclosingCircle源码--求最小包围圆的算法
- Opencv源码之平面点集的最小包围圆
- 退火算法求最小包围圆
- opencv3寻找最小包围圆形-minEnclosingCircle函数
- Ritter's求最小包围圆
- 【OpenCV笔记 15-2】OpenCV寻找物体最小包围矩形和最小包围圆
- 物体最小包围矩形,最小包围圆
- 创建包围轮廓的矩形和圆形边界框--boundingRect()、minEnclosingCircle()和approxPolyDP()
- 创建包围轮廓的矩形和圆形边界框--boundingRect()、minEnclosingCircle()和approxPolyDP()
- HDU3995 最小包围圆变形
- hdu 3007 最小包围圆
- matlab 实现最小包围圆
- 平面点集的最小包围圆 hdu 3932
- 一种快速收敛的自适应算法求解最小包围盒(OBB)
- AE求要素的包围盒
- 求点序列的包围矩形
- poj 1113 Wall 【求包围所有点、且与所有点距离至少为L的曲线的最小周长】
- 【opencv练习34 - 轮廓包围圆,矩形】
- 王者荣耀高并发背后的故事
- 解决FTP文件访问需要输入用户名和密码的问题
- ORA-12541: TNS: 无监听程序 的解决办法
- Jzoj4841 平衡的子集
- SQL语句中----删除表数据drop、truncate和delete的用法
- Opencv--minEnclosingCircle源码--求最小包围圆的算法
- (机器学习基石)Machine Learning Foundations:Lecture 1
- dwr前后端交互的一个完整demo(新手)
- JS事件流(W3C与IE区别)
- 作用域
- install opencv and dlib in anaconda
- Java 异常
- Tensorflow-图像预处理
- 手把手教你Chrome浏览器安装Postman(含下载云盘链接)【转载】