OpenCV 版本:4.5.0
函数名:rotatedRectangleIntersection
功能:计算两个旋转矩形的位置关系(是否重叠),计算重叠区域
函数声明所在文件:imgproc.hpp
函数声明:
/** @brief Finds out if there is any intersection between two rotated rectangles.
If there is then the vertices of the intersecting region are returned as well.
Below are some examples of intersection configurations. The hatched pattern indicates the
intersecting region and the red vertices are returned by the function.
![intersection examples](pics/intersection.png)
@param rect1 First rectangle
@param rect2 Second rectangle
@param intersectingRegion The output array of the vertices of the intersecting region. It returns
at most 8 vertices. Stored as std::vector\<cv::Point2f\> or cv::Mat as Mx1 of type CV_32FC2.
@returns One of #RectanglesIntersectTypes
*/
CV_EXPORTS_W int rotatedRectangleIntersection( const RotatedRect& rect1, const RotatedRect& rect2, OutputArray intersectingRegion );
函数定义所在文件:intersection.cpp
函数定义:
int rotatedRectangleIntersection( const RotatedRect& rect1, const RotatedRect& rect2, OutputArray intersectingRegion )
{
CV_INSTRUMENT_REGION();
// L2 metric
const float samePointEps = std::max(1e-16f, 1e-6f * (float)std::max(rect1.size.area(), rect2.size.area()));
Point2f vec1[4], vec2[4];
Point2f pts1[4], pts2[4];
std::vector <Point2f> intersection; intersection.reserve(24);
rect1.points(pts1);
rect2.points(pts2);
int ret = INTERSECT_FULL;
// Specical case of rect1 == rect2
{
bool same = true;
for( int i = 0; i < 4; i++ )
{
if( fabs(pts1[i].x - pts2[i].x) > samePointEps || (fabs(pts1[i].y - pts2[i].y) > samePointEps) )
{
same = false;
break;
}
}
if(same)
{
intersection.resize(4);
for( int i = 0; i < 4; i++ )
{
intersection[i] = pts1[i];
}
Mat(intersection).copyTo(intersectingRegion);
return INTERSECT_FULL;
}
}
// Line vector
// A line from p1 to p2 is: p1 + (p2-p1)*t, t=[0,1]
for( int i = 0; i < 4; i++ )
{
vec1[i].x = pts1[(i+1)%4].x - pts1[i].x;
vec1[i].y = pts1[(i+1)%4].y - pts1[i].y;
vec2[i].x = pts2[(i+1)%4].x - pts2[i].x;
vec2[i].y = pts2[(i+1)%4].y - pts2[i].y;
}
// Line test - test all line combos for intersection
for( int i = 0; i < 4; i++ )
{
for( int j = 0; j < 4; j++ )
{
// Solve for 2x2 Ax=b
float x21 = pts2[j].x - pts1[i].x;
float y21 = pts2[j].y - pts1[i].y;
float vx1 = vec1[i].x;
float vy1 = vec1[i].y;
float vx2 = vec2[j].x;
float vy2 = vec2[j].y;
float det = vx2*vy1 - vx1*vy2;
float t1 = (vx2*y21 - vy2*x21) / det;
float t2 = (vx1*y21 - vy1*x21) / det;
// This takes care of parallel lines
if( cvIsInf(t1) || cvIsInf(t2) || cvIsNaN(t1) || cvIsNaN(t2) )
{
continue;
}
if( t1 >= 0.0f && t1 <= 1.0f && t2 >= 0.0f && t2 <= 1.0f )
{
float xi = pts1[i].x + vec1[i].x*t1;
float yi = pts1[i].y + vec1[i].y*t1;
intersection.push_back(Point2f(xi,yi));
}
}
}
if( !intersection.empty() )
{
ret = INTERSECT_PARTIAL;
}
// Check for vertices from rect1 inside recct2
for( int i = 0; i < 4; i++ )
{
// We do a sign test to see which side the point lies.
// If the point all lie on the same sign for all 4 sides of the rect,
// then there's an intersection
int posSign = 0;
int negSign = 0;
float x = pts1[i].x;
float y = pts1[i].y;
for( int j = 0; j < 4; j++ )
{
// line equation: Ax + By + C = 0
// see which side of the line this point is at
float A = -vec2[j].y;
float B = vec2[j].x;
float C = -(A*pts2[j].x + B*pts2[j].y);
float s = A*x+ B*y+ C;
if( s >= 0 )
{
posSign++;
}
else
{
negSign++;
}
}
if( posSign == 4 || negSign == 4 )
{
intersection.push_back(pts1[i]);
}
}
// Reverse the check - check for vertices from rect2 inside recct1
for( int i = 0; i < 4; i++ )
{
// We do a sign test to see which side the point lies.
// If the point all lie on the same sign for all 4 sides of the rect,
// then there's an intersection
int posSign = 0;
int negSign = 0;
float x = pts2[i].x;
float y = pts2[i].y;
for( int j = 0; j < 4; j++ )
{
// line equation: Ax + By + C = 0
// see which side of the line this point is at
float A = -vec1[j].y;
float B = vec1[j].x;
float C = -(A*pts1[j].x + B*pts1[j].y);
float s = A*x + B*y + C;
if( s >= 0 )
{
posSign++;
}
else
{
negSign++;
}
}
if( posSign == 4 || negSign == 4 )
{
intersection.push_back(pts2[i]);
}
}
int N = (int)intersection.size();
if (N == 0)
{
return INTERSECT_NONE;
}
// Get rid of duplicated points
int Nstride = N;
cv::AutoBuffer<float, 100> distPt(N * N);
cv::AutoBuffer<int> ptDistRemap(N);
for (int i = 0; i < N; ++i)
{
const Point2f pt0 = intersection[i];
ptDistRemap[i] = i;
for (int j = i + 1; j < N; )
{
const Point2f pt1 = intersection[j];
float d2 = normL2Sqr<float>(pt1 - pt0);
if(d2 <= samePointEps)
{
if (j < N - 1)
intersection[j] = intersection[N - 1];
N--;
continue;
}
distPt[i*Nstride + j] = d2;
++j;
}
}
while (N > 8) // we still have duplicate points after samePointEps threshold (eliminate closest points)
{
int minI = 0;
int minJ = 1;
float minD = distPt[1];
for (int i = 0; i < N - 1; ++i)
{
float* pDist = distPt.data() + Nstride * ptDistRemap[i];
for (int j = i + 1; j < N; ++j)
{
float d = pDist[ptDistRemap[j]];
if (d < minD)
{
minD = d;
minI = i;
minJ = j;
}
}
}
CV_Assert(fabs(normL2Sqr<float>(intersection[minI] - intersection[minJ]) - minD) < 1e-6); // ptDistRemap is not corrupted
// drop minJ point
if (minJ < N - 1)
{
intersection[minJ] = intersection[N - 1];
ptDistRemap[minJ] = ptDistRemap[N - 1];
}
N--;
}
// order points
for (int i = 0; i < N - 1; ++i)
{
Point2f diffI = intersection[i + 1] - intersection[i];
for (int j = i + 2; j < N; ++j)
{
Point2f diffJ = intersection[j] - intersection[i];
if (diffI.cross(diffJ) < 0)
{
std::swap(intersection[i + 1], intersection[j]);
diffI = diffJ;
}
}
}
intersection.resize(N);
Mat(intersection).copyTo(intersectingRegion);
return ret;
}
源码阅读理解:
- 主要思路:
- 计算两个旋转矩形边的交点,是线段的交点,而非直线的交点
- 计算矩形四个顶点在另一个矩形内或边上的点
- 将上述两种点合并去重,即构成了交叉区域的顶点
- 源码解释:
- 变量解释:
vector <Point2f> intersection
存放目标点Point2f pts1[4], pts2[4]
存放矩形顶点Point2f vec1[4], vec2[4]
存放Δx,Δy,方便后续计算vec1[i].x = pts1[(i+1)%4].x - pts1[i].x;
vec1[i].y = pts1[(i+1)%4].y - pts1[i].y;
vec2[i].x = pts2[(i+1)%4].x - pts2[i].x;
vec2[i].y = pts2[(i+1)%4].y - pts2[i].y;
- 公式解释:
- 计算两线段交点时{已知矩形的一条边两个顶点
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
(x_1,y_1),(x_2,y_2)
(x1,y1),(x2,y2)和另一个矩形的一条边两个顶点
(
x
3
,
y
3
)
,
(
x
4
,
y
4
)
(x_3,y_3),(x_4,y_4)
(x3,y3),(x4,y4)}:
- 交点横坐标 X c r o s s = x 1 + ( x 2 − x 1 ) ∗ t 1 = x 3 + ( x 4 − x 3 ) ∗ t 2 X_{cross}=x_1+(x_2-x_1)*t_1=x_3+(x_4-x_3)*t2 Xcross=x1+(x2−x1)∗t1=x3+(x4−x3)∗t2
- 交点纵坐标 Y c r o s s = y 1 + ( y 2 − y 1 ) ∗ t 1 = y 3 + ( y 4 − y 3 ) ∗ t 2 Y_{cross}=y_1+(y_2-y_1)*t_1=y_3+(y_4-y_3)*t2 Ycross=y1+(y2−y1)∗t1=y3+(y4−y3)∗t2
- 由于已知 x 1 , x 2 , x 3 , x 4 , y 1 , y 2 , y 3 , y 4 x_1,x_2,x_3,x_4,y_1,y_2,y_3,y_4 x1,x2,x3,x4,y1,y2,y3,y4,上述两个等式则变为二元一次方程组, t 1 , t 2 t_1,t_2 t1,t2为未知数
- 解二元一次方程组:
- t 1 = ∣ x 3 − x 1 − x 4 − x 3 y 3 − y 1 − y 4 − y 3 ∣ ∣ x 2 − x 1 − x 4 − x 3 y 2 − y 1 − y 4 − y 3 ∣ t_1=\frac{|\begin{matrix} x_3-x_1&-x_4-x_3\\ y_3-y_1&-y_4-y_3\end{matrix}|}{|\begin{matrix} x_2-x_1&-x_4-x_3\\ y_2-y_1&-y_4-y_3\end{matrix}|} t1=∣x2−x1y2−y1−x4−x3−y4−y3∣∣x3−x1y3−y1−x4−x3−y4−y3∣
- t 2 = ∣ x 2 − x 1 x 3 − x 1 y 2 − y 1 y 3 − y 1 ∣ ∣ x 2 − x 1 − x 4 − x 3 y 2 − y 1 − y 4 − y 3 ∣ t_2=\frac{|\begin{matrix} x_2-x_1&x_3-x_1\\ y_2-y_1&y_3-y_1\end{matrix}|}{|\begin{matrix} x_2-x_1&-x_4-x_3\\ y_2-y_1&-y_4-y_3\end{matrix}|} t2=∣x2−x1y2−y1−x4−x3−y4−y3∣∣x2−x1y2−y1x3−x1y3−y1∣
- x 2 − x 1 = v x 1 x_2-x_1=vx1 x2−x1=vx1
- x 4 − x 3 = v x 2 x_4-x_3=vx2 x4−x3=vx2
- y 2 − y 1 = v y 1 y_2-y_1=vy1 y2−y1=vy1
- y 4 − y 3 = v y 2 y_4-y_3=vy2 y4−y3=vy2
- x 3 − x 1 = x 21 x_3-x_1=x21 x3−x1=x21
- y 3 − y 1 = y 21 y_3-y_1=y21 y3−y1=y21
- d e t = ∣ x 2 − x 1 − x 4 − x 3 y 2 − y 1 − y 4 − y 3 ∣ = v x 2 ∗ v y 1 − v x 1 ∗ v y 2 det=|\begin{matrix} x_2-x_1&-x_4-x_3\\ y_2-y_1&-y_4-y_3\end{matrix}|=vx2*vy1-vx1*vy2 det=∣x2−x1y2−y1−x4−x3−y4−y3∣=vx2∗vy1−vx1∗vy2
- 于是, t 1 = v x 2 ∗ y 21 − v y 2 ∗ x 21 d e t t_1=\frac{vx2*y21 - vy2*x21}{det} t1=detvx2∗y21−vy2∗x21, t 2 = v x 1 ∗ y 21 − v y 1 ∗ x 21 d e t t_2=\frac{vx1*y21 - vy1*x21}{det} t2=detvx1∗y21−vy1∗x21
- 计算点在线的哪一侧:
- 已知两点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2)构成直线的两点式为: y − y 1 y 2 − y 1 = x − x 1 x 2 − x 1 \frac{y-y_1}{y_2-y_1}=\frac{x-x_1}{x_2-x_1} y2−y1y−y1=x2−x1x−x1
- 转换为 A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0的形式为: − v y 1 ∗ x + v x 1 ∗ y − x 1 ∗ v y 1 − y 1 ∗ v x 1 -vy1*x+vx1*y-x_1*vy1-y_1*vx1 −vy1∗x+vx1∗y−x1∗vy1−y1∗vx1
- 点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)到线 A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0的距离公式为: d = ∣ A x 0 + B y 0 + C ∣ A 2 + B 2 d=\frac{|Ax_0+By_0+C|}{\sqrt{A^2+B^2}} d=A2+B2∣Ax0+By0+C∣
- 点在线 A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0的哪一侧就根据 A x 0 + B y 0 + C Ax_0+By_0+C Ax0+By0+C的符号决定
- 这里需要注意的是, A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0与 − A x − B y − C = 0 -Ax-By-C=0 −Ax−By−C=0表示同一条直线,然而 A x 0 + B y 0 + C Ax_0+By_0+C Ax0+By0+C与 − A x 0 − B y 0 − C -Ax_0-By_0-C −Ax0−By0−C却互为相反数;一般我们习惯将两点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2)满足 x 1 < x 2 x_1<x_2 x1<x2时使用两点式 y − y 1 y 2 − y 1 = x − x 1 x 2 − x 1 \frac{y-y_1}{y_2-y_1}=\frac{x-x_1}{x_2-x_1} y2−y1y−y1=x2−x1x−x1,将其移项到左边获得的标准式 ( y 2 − y 1 ) ∗ x + ( x 2 − x 1 ) ∗ y − x 1 ∗ ( y 2 − y 1 ) − y 1 ∗ ( x 2 − x 1 ) = 0 (y_2-y_1)*x+(x_2-x_1)*y-x_1*(y_2-y_1)-y_1*(x_2-x_1)=0 (y2−y1)∗x+(x2−x1)∗y−x1∗(y2−y1)−y1∗(x2−x1)=0,此时点在直线下方则 A x 0 + B y 0 + C < 0 Ax_0+By_0+C<0 Ax0+By0+C<0,点在直线上方则 A x 0 + B y 0 + C > 0 Ax_0+By_0+C>0 Ax0+By0+C>0
- 源码里面判断点是否在矩形内,使用两点式的两点带有顺序,固可以通过 A x 0 + B y 0 + C Ax_0+By_0+C Ax0+By0+C是否均大于等于零或是否均小于零来判断
- 计算两线段交点时{已知矩形的一条边两个顶点
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
(x_1,y_1),(x_2,y_2)
(x1,y1),(x2,y2)和另一个矩形的一条边两个顶点
(
x
3
,
y
3
)
,
(
x
4
,
y
4
)
(x_3,y_3),(x_4,y_4)
(x3,y3),(x4,y4)}:
- 补充说明:
- 对于目标点集进行去重:
- 设置距离阈值,当两个点之间距离小于该阈值认为这两个点重合,认为是同一个点,去除重复点
- 两个旋转矩形相交,重叠区域的顶点数量最多为8,可能由于设置阈值太小,经过去除重复点,点集数量大于8,有些点之间距离虽然大于该阈值, 但是认为是没有必要的,只需要最后不超过8个点即可,于是按照距离从小到大,将距离最小的两个点去掉其中一个,直到最后点集中剩下不超过8个点
- 对不超过8的点集排序:
- 根据二维向量叉乘的关系: P × Q > 0 P×Q>0 P×Q>0表示向量P在向量Q的顺时针方向
- 源码对点集进行排序,将重叠区域顶点按照从某个点出发,为逆时针顺序排序
- 最后将点集转换到列为1的矩阵中
- 对于目标点集进行去重:
- 变量解释: