问题
语言 :C++
OpenCV版本:3.4.0
在目标检测中,后处理阶段会用到非极大值抑制来过滤目标框,而计算两个框的IOU(交并比)则是其关键的一环,先计算两个框相交的点,再求出这些点构成的多边形的面积就是这两个框相交的面积。
cv::rotatedRectangleIntersection(cv::RotatedRect r1, cv::RotatedRect r1, vector<cv::Point2f> vertices)用来计算两个旋转矩形框的相交多边形,返回的结果vertices是多边形的点坐标,最多返回8个点的坐标。
再通过double cv::contourArea(InputArray contour, bool oriented=false)计算多边形面积,这样就得到了相交多边形的面积。
但是实际应用中发现contourArea计算的面积不正确,通过排查发现是rotatedRectangleIntersection生成的点坐标不是顺时针或者逆时针的顺序(并不是所有情况下都是,而是某些小部分情况下),画张图来解释下,rotatedRectangleIntersection函数返回的点坐标顺序可能是125463,这样的顺序会导致contourArea函数计算的面积是错误的,当然这并不是contourArea函数的bug,因为这个函数计算时是按照点的顺序(方向)来计算轮廓的面积,所以需要重新对坐标顺序进行排列,使得坐标按照顺时针或者逆时针排序,从任意一点开始都可以,比如123456,234561,543216顺序都可以。
解决
- 解决的方法比较简单,就是将所有点按照顺时针或逆时针排序即可,哪个点作为起始点都可以。
- 我的思路是先找到最左侧的点,然后计算该点与其他所有点的arc tan(反正切)值,然后再从大到小或从小到大排序就可以了,画个图更直观的解释下
- 在x轴(虚线)上方的反正切值为正数且角度越大反正切值越大,在x轴(虚线)上方的反正切值为负数数且角度越大反正切值越小,这样就可以对坐标点排序了。
代码
#include <vector>
#include <math.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
typedef struct{
float x;
float y;
float w;
float h;
float theta;
float score;
int label;
}detection;
// 第一步找到最左边的点
int find_leftmost_point(vector<cv::Point2f> intersectingRegion)
{
int index = 0;
float tmp = intersectingRegion[0].x;
for(int i=1; i<intersectingRegion.size(); i++)
{
if(intersectingRegion[i].x < tmp)
{
tmp = intersectingRegion[i].x;
index = i;
}
}
return index;
}
//第二步对所有点进行排序
vector<cv::Point2f> sort_points(vector<cv::Point2f> intersectingRegion)
{
vector<cv::Point2f> sort_intersectingRegion;
int leftmost_index = find_leftmost_point(intersectingRegion);
vector<float> arctan;
for(int i=0; i<intersectingRegion.size(); i++)
{
arctan.push_back(atan2(intersectingRegion[i].y - intersectingRegion[leftmost_index].y, intersectingRegion[i].x - intersectingRegion[leftmost_index].x));
}
vector<int> index;
for(int i=0; i<arctan.size(); i++)
{
index.push_back(i);
}
sort(index.begin(), index.end(), [&](const int& a, const int& b) {return (arctan[a] < arctan[b]);});
for(int i=0; i<index.size(); i++)
{
sort_intersectingRegion.push_back(intersectingRegion[index[i]]);
}
return sort_intersectingRegion;
}
// 计算两个旋转框的IOU
float rbox_iou(detection d1, detection d2)
{
float inter_area;
float area_r1 = d1.w * d1.h;
float area_r2 = d2.w * d2.h;
cv::RotatedRect rect1;
rect1.center = cv::Point2f(d1.x, d1.y);
rect1.size = cv::Size(d1.w, d1.h);
rect1.angle = d1.theta;
cv::RotatedRect rect2;
rect2.center = cv::Point2f(d2.x, d2.y);
rect2.size = cv::Size(d2.w, d2.h);
rect2.angle = d2.theta;
vector<cv::Point2f> intersectingRegion;
cv::rotatedRectangleIntersection(rect1, rect2, intersectingRegion);
if (intersectingRegion.empty())
{
inter_area = 0;
}
else
{
vector<cv::Point2f> sort_intersectingRegion = sort_points(intersectingRegion);
inter_area = cv::contourArea(sort_intersectingRegion);
}
return inter_area / (area_r1 + area_r2 - inter_area + 0.00000001);
}