Convex Hull Algorithm for Point Sets

Question: On a two-dimensional plane, given some points with coordinate positions, how to obtain the smallest convex polygon containing all these points?

The problem is to find a convex hull ( Convex Hull ) on a two-dimensional plane .

As shown in the figure below, some points are given in the figure, and the convex shape surrounded by the peripheral lines just surrounds all the given points.

If we observe these points and convex polygons in a two-dimensional plane coordinate system.

We can find that among these points, along the abscissa direction, the points with the largest and smallest x-coordinate values ​​must be on the convex hull. Similarly, along the ordinate direction, the points with the largest and smallest y-coordinate values ​​must also be on the convex hull. Because this is defined by the convex hull itself, and the convex hull is the outermost circle of points, so the outermost points must be the extremum in all directions.

Then, viewed from point A, the points of the given set are all on the side of side AD and also on the side of side AB. Likewise, points on the convex hull satisfy this requirement. How can we take advantage of this feature?

Let's take advantage of it first through angles.

Taking point A as an example, the angle formed by the side AB and the positive direction of the x-axis is the smallest among all the angles formed by the sides formed by all points in the set and A and the positive direction of the x-axis, and the side AE ​​and the positive direction of the x-axis The angle formed is the largest among all angles formed by all points in the set and the side formed by A and the positive direction of the x-axis.

In a simple description, starting from point A, draw a ray along the direction parallel to the x-axis. The ray rotates counterclockwise around point A, then point B is the first point that the ray hits, and point E is the The point where the ray was last touched. As shown below:

 Taking point B as an example again, if starting from point B, draw a ray along the direction of side AB, and the ray rotates counterclockwise around point B, then point C is the first point that the ray hits (the angle is the smallest) , and point A is the last point this ray hits (with the largest angle). As shown below:

 

Analyzing other points on the convex hull in turn, all satisfy this property.

We use this feature to find the convex hull. step:

1) The first step is to find the point in the lower left corner (that is, the point with the smallest y-axis coordinate value and the smallest x-axis coordinate value) P0, which must be a point on the convex hull;

2) Other points in the point set are connected with point P0 to form a side, calculate the angle formed between these sides and the positive direction of the x-axis, and take the point with the smallest angle as the next point P1 on the convex hull;

3) Then use point P0 and point P1 to form a vector \overrightarrow{P0P1}, and all points in the point set except point P0 and point P1 form a vector with point P1, and calculate the angle between these vectors and vectors, and \overrightarrow{P0P1}take the point with the smallest angle as The next point P2 on the convex hull;

4) By analogy, repeat the operation in step 3, each time taking the point with the smallest included angle as the next point of the convex hull. Until the next convex hull point obtained from a point is equal to point P0, and return to the starting point, the calculation will stop.

The flow chart is as follows:

This method is actually  the basic idea of ​​Jarvis stepping method .

 So how to find the angle between two vectors?

In fact, we don't need to find the real included angle here, we just need to get the point with the smallest included angle, which means it is a result of relative comparison. Look at the picture below:

The angle formed by the vector \overrightarrow{OP1}and the positive direction of the x-axis \alpha\overrightarrow{OP2}the angle formed by the vector and the positive direction of the x-axis \beta, the angle \overrightarrow{OP3}formed by the vector and the positive direction of the x-axis \theta, \angle \alpha< \angle \beta< \angle \theta.

From the perspective of line segment OP1, \angle \alpha<< is because point P2 and point P3 \angle \betaare \angle \thetaboth on the upper left of line segment OP1.

Converted to the angle of the vector, from the perspective of the vector , the value of \overrightarrow{OP1}the vector is > 0, the value of > 0, and the value of > 0.\overrightarrow{OP1}\times\overrightarrow{OP2}\overrightarrow{OP1}\times\overrightarrow{OP3}\overrightarrow{OP2}\times\overrightarrow{OP3}

Let's go back to the original picture

 When we start to calculate the next convex hull node at point B, the \overrightarrow{BC}result of the vector cross product vector formed by other points in the point set and point B is <0, which in turn means that the vector \overrightarrow{BC}cross product of other vectors is >0 of.

Execution flow converted into code

1) Start with point B as the tail of the vector, and any other point in the point set as the next node of the convex hull, and as the head of the vector to form a vector BP(next);

2) Then traverse other points in the remaining point set, and form a vector BP(i) with point B

Vector BP(next) Cross product vector BP(i)

3) If the result of cross product > 0, do not process;

If the cross product result = 0, it means that point P(next) and point P(i) are collinear, then take the point that is farthest from point B among the two points, and replace the point selected at the beginning;

If the cross product result is <0, replace point P(next) with point P(i) as the next node of the convex hull.

So far, after we have the execution process of one point, we can extend it to the global process of all points.

Let's rearrange the entire flow chart again, as follows:

 

The key code is as follows:

 1) In the case of collinearity, find out the far point

#define SEGMENTLEN(x0,y0,x1,y1) (sqrt(pow(((x1)-(x0)), 2.0) + pow(((y1)-(y0)), 2.0)))

 2) The position of the judgment point

qreal Convex::comparePointClock(const QPointF &point_0, const QPointF &point_c, const QPointF &point_i)
{
	return ((point_i.x() - point_0.x())*(point_c.y() - point_0.y()) - (point_i.y() - point_0.y())*(point_c.x() - point_0.x()));
}

 3) Get the minimum coordinates

QPointF Convex::getMinimumPoint(const QVector<QPointF> &vecPoints)
{
	if (vecPoints.isEmpty())
		return QPointF();
 
	QPointF minPoint = vecPoints.at(0);
	quint16 point_x = vecPoints.at(0).x(), point_y = vecPoints.at(0).y();
	for (QVector<QPointF>::const_iterator it = vecPoints.constBegin(); it != vecPoints.constEnd(); it++)
	{
		//比较Y坐标,找Y坐标最小的
		if (it->y() < minPoint.y())
		{
			minPoint = (*it);
		}
		else
		{
			//Y坐标相同,找X坐标小的
			if (it->y() == minPoint.y() && it->x() < minPoint.x())
			{
				minPoint = (*it);
			}
		}
	}
 
	return minPoint;
}

4) Core processing business

//Jarvis's march 算法,O(nH),H为点的个数。
qint8 Convex::getConvexHullJarvis(const QVector<QPointF> &vecSourPoints, QVector<QPointF> &vecTarPoints)
{
	if (vecSourPoints.isEmpty())
		return -1;
 
	QPointF minPoint;
	QPointF lowPoint, point_0, point_i, point_c;
	qreal count = 0,z = 0;
	qreal length_1, length_2;
	QVector<QPointF> tempVecPoint(vecSourPoints);
 
	vecTarPoints.clear();
	//删除重复坐标
	if (removeRepeatPoints(tempVecPoint) <= 0)
		return -1;
 
	//查找最小坐标
	minPoint = getMinimumPoint(tempVecPoint);
	lowPoint = minPoint;
	
	point_0 = lowPoint;
 
	do {
		//起始点point_0压入凸包点集中
		vecTarPoints.push_back(point_0);
		
		count = 0;
		for (QVector<QPointF>::iterator it = tempVecPoint.begin(); it != tempVecPoint.end(); it++)
		{
			//跳过起始坐标
			if ((*it) == point_0)
				continue;
 
			count++;
			if (count == 1) //把第一个遍历的点作为point_c
			{
				point_c = (*it);
				continue;
			}
			//如果z>0则point在point_i和point_c连线的下方,z<0则point_i在连线的上方,z=0则point_i共线
			z = comparePointClock(point_0,point_c,(*it));//((it->x() - point_0.x())*(point_c.y() - point_0.y()) - (it->y() - point_0.y())*(point_c.x() - point_0.x()));
			if (z > 0)
			{
				point_c = (*it);
			}
			else if (z == 0)
			{
				//共线情况找出距离point_0较远的那个点作为point_c
				length_1 = SEGMENTLEN(point_0.x(),point_0.y(),it->x(),it->y());
				length_2 = SEGMENTLEN(point_0.x(), point_0.y(), point_c.x(), point_c.y());
				if (length_1 > length_2)
				{
					point_c = (*it);
				}
			}
		}
		point_0 = point_c;
 
	} while (point_0 != lowPoint);
	vecTarPoints.push_back(lowPoint);
	if (vecTarPoints.isEmpty())
		return -1;
	return 0;
}

Guess you like

Origin blog.csdn.net/m0_74178120/article/details/128753677