最近看了最小凸包绘制,官方代码没看,常见的有Graham's scan算法
其实原理比较简单,先将点集排序,之后根据左转进行栈扫描。
1.点集排序:一般取x坐标最小的,并且尽量y大的,靠左下角的点作为起始点。然后连接起始点P0与其他点,计算连线与竖直向下方向的夹角,按照大小顺序排列。
2.然后根据排序之后的点,依次判定该点是否左转,若是左转,入栈,否则,取下一点。判断左转可以根据矢量面积法:
S(P1,P2,P3)=|y1 y2 y3|= (x1-x3)*(y2-y3)-(y1-y3)*(x2-x3) 第一点为P1,向量方向为P1P2,判断P3点,若S为正,P3在向量P1P2的左侧。
1.点集排序:
void sort(vector<Point>& pss)
{
//pss是装着原本的点
//bound是排好序的点
//首先排序
int count = pss.size();
int first_Point = 0;
float* Aangle = new float[count]; //动态数组存放角度
int Min_x = pss[0].x ;
int Max_y = pss[0].y ;
for(int i=0;i<pss.size();i++) //取横坐标最小的点 y最大
{
if ( (pss[i].x < Min_x)||((pss[i].x == Min_x)&&(pss[i].y>Max_y)))
{
Min_x = pss[i].x;
Max_y = pss[i].y;
first_Point = i;
}
}
Point temp = pss[0]; //将起始点放到首位置
pss[0] = pss[first_Point];
pss[first_Point]= temp;
//计算连线与竖直方向夹角
for(int i=0;i<pss.size();i++)
{
float angle = -1.0* (float)(pss[i].y -pss[0].y )/(float)(pss[i].x -pss[0].x );//图像坐标系有点区别
Aangle[i] = angle;
}
//根据夹角排序
for(int i = 1;i<count-1;i++)
{
for(int j = i+1;j<count;j++)
{
if(Aangle[j]<Aangle[i])
{
float temp = Aangle[i];
Aangle[i] = Aangle[j];
Aangle[j] = temp;
Point temp1 =pss[i];
pss[i] = pss[j];
pss[j] = temp1;
}
}
}
delete []Aangle;
}
2.栈扫描
void scan(vector<Point>& pss,vector<Point>& bound)
{
int i = 1;//记录拐点索引
int pop_count= 0;//记录弹出次数
bound.push_back(pss[0]);
bound.push_back(pss[1]);
bound.push_back(pss[2]);
//根据已排好序列的点连接线段
do
{
//计算夹角
float angle = ((bound[i].y - bound[i+1].y)*(bound[i-1].x - bound[i+1].x) - (bound[i-1].y - bound[i+1].y)*(bound[i].x - bound[i+1].x));
if(angle<=0.0)//左转
{
bound.push_back(pss[(i+2+pop_count)%pss.size()]);// 压栈
i++;//索引加1
}
else//右转
{
bound.erase(bound.end()-2);//弹出倒数第二个点
pop_count++;//弹出次数加1
i--;.//索引减1
}
} while (pss[0] != *bound.rbegin() );//回到起始点
}
测试:
void test(vector<Point> point)
{
vector<Point> bound;
sort(point);
scan(point,bound);
for (int i =0;i<bound.size()-1;i++)
{
line(img,bound[i],bound[i+1],Scalar(0,0,255),2,8,0);
}
line(img,bound[0],bound[bound.size()-1],Scalar(0,0,255),2,8,0);
imshow("dst",img);
}
结果: