PS:要轉載請注明出處,本人版權所有。
PS: 這個只是基於《我自己》的理解,
如果和你的原則及想法相沖突,請諒解,勿噴。
前置說明
本文作為本人csdn blog的主站的備份。(BlogID=115)
環境說明
無
前言
提到IOU,如果接觸過目標檢測,應該是很熟悉的,這個東西簡直就是標配了。但是我之前見到的求IOU都是求兩個矩形的IOU,由於矩形的特殊性,其IOU可以很簡單的求。
突然有一天,我要求一個矩形和一個多邊形的IOU,這就讓我突然有點懵,參考原來求兩個矩形的IOU方式,完全無解。經過了詢問大佬以及網上沖浪后,在某不起眼的地方發現了一個思路,一個匿名網友貌似提供了一句MATLAB的代碼,給了我不錯的啟發。
因此本文用c++和opencv實現了這部分代碼。下面將會從IOU概念,兩矩形的IOU,以及任意多邊形之間的IOU順序進行講解。
交並比(Intersection of Union,IOU)
我們定義一個多邊形的面積為Area0,另外一個多邊形的面積為Area1。那么IOU的數學定義為\(IOU=\frac{Area_0 \cap Area_1}{Area_0 \cup Area_1}\)。
下面我們用一個示例圖清晰的表達這個概念:

其中B類區域就是兩個多邊形的交集。IOU求的是B類區域在A,B,C類區域中的占比。注意這里是占比。
兩個矩形之間的IOU求法
兩個矩形的IOU求法其實交簡單,根據一些性質,我們可以直接求出交集區域的寬和高,然后直接得到交集面積即可,這些都是常規寫法。詳情見如下代碼:
float IOU(const cv::Rect &r0, const cv::Rect &r1)
{
if (r0.x > r1.x + r1.width) return 0.f;//top-x r0 在r1右邊
if (r0.y > r1.y + r1.height) return 0.f;//top-y r0 在r1下邊
if (r0.x + r0.width < r1.x) return 0.f;//bottom-x r0 在r1左邊
if (r0.y + r0.height < r1.y) return 0.f;//bottom-y r0 在r1上邊
// 此時必定相交
float _overlap_w = std::min(r0.x + r0.width, r1.x + r1.width) - std::max(r0.x, r1.x);//得到相交矩形w
float _overlap_h = std::min(r0.y + r0.height, r1.y + r1.height) - std::max(r0.y, r1.y);//得到相交矩形h
// maybe overflow
return (_overlap_w * _overlap_h)/(float)((r0.width*r0.height) + (r1.width*r1.height));
}
如上可知,可以直接根據相關面積,求出IOU。
兩個多邊形之間的IOU求法
我們需要求兩個多邊形的IOU,這個時候我們根據兩個矩形的思路想想,貌似不好弄。這個時候我們可以轉變一下思路。
我們首先肯定是知道兩個多邊形的最大外接矩形的,這個時候我們得到最大的外界矩形寬和高(注意,這里的寬和高一般是固定的,所以一般我們都不需要去求外接矩)。然后我們創建三個寬和高等於我們預設的一維矩陣M_A, M_B,M_C,並將其所有元素置為0。這個時候,我們分別將矩陣一用多邊形P1來來填充M_A,在P1內的元素置為1,外的元素不變。對M_B用同樣的方式去填充。這個時候我們去計算M_C,具體計算方法是將對應的M_A,M_B同一位置的元素相與后賦值給M_C(表達式為:\(M_C(x,y) = M_A(x,y) \& M_B(x,y)\))。這個時候我們去統計M_A,M_B,M_C中1的個數,其實就得到了對應的面積(也可以叫做像素面積),這個時候我們就可以方便的求出IOU,值得注意的是opencv中提供了我們所需要的所有操作。
下面我們用c++來實現以上的流程(注意,以下代碼是實現的是N個多邊形和M個多邊形的IOU,若自己的需求不一樣,請修改為對應的場景,代碼是隨手寫的,未仔細驗證,原理是這樣的)。
float IoU(const std::vector<std::vector<cv::Point>> &poly_array0, const std::vector<std::vector<cv::Point>> &poly_array1, int max_w, int max_h){
cv::Mat _poly0 = cv::Mat::zeros(max_h, max_w, CV_8UC1);
cv::Mat _poly1 = cv::Mat::zeros(max_h, max_w, CV_8UC1);
cv::Mat _result;
std::vector<cv::Point *> _pts0;
std::vector<int> _npts0;
for(auto &_v:poly_array0){
if (_v.size() < 3)//invalid poly
return -1.f;
_pts0.push_back((cv::Point *)&_v[0]);
_npts0.push_back((int)_v.size());
}
std::vector<cv::Point *> _pts1;
std::vector<int> _npts1;
for(auto &_v:poly_array1){
if (_v.size() < 3)//invalid poly
return -1.f;
_pts1.push_back((cv::Point *)&_v[0]);
_npts1.push_back((int)_v.size());
}
/*
void cv::fillPoly ( Mat & img,
const Point ** pts,
const int * npts,
int ncontours,
const Scalar & color,
int lineType = LINE_8,
int shift = 0,
Point offset = Point()
)
*/
cv::fillPoly(_poly0, (const cv::Point **)&_pts0[0], &_npts0[0], _npts0.size(), cv::Scalar(1));
cv::fillPoly(_poly0, (const cv::Point **)&_pts1[0], &_npts1[0], _npts1.size(), cv::Scalar(1));
cv::bitwise_and(_poly0, _poly1, _result);
int _area0 = cv::countNonZero(_poly0);
int _area1 = cv::countNonZero(_poly1);
int _intersection_area = cv::countNonZero(_result);
// float _iou = (float)_intersection_area/(float)(_area0 + _area1 - _intersection_area);
float _iou = (float)_intersection_area/(float)_area1;
return _iou;
}
通過如上代碼,我們用opencv就實現了我們預想的效果。
后記
注意,這里需要延伸一點,求IOU,我們一般是用交集除以並集,但是有些時候,可能我們會用交集除以其中一個集合。比如:一個小矩形,一個大多邊形的iou,其實按照標准寫法來看,iou的數值不處理的話,不是那么可愛的。
其實這里的幾種用法還可以延伸出來,比如算兩個立方體的重合度等等。
參考文獻

PS: 請尊重原創,不喜勿噴。
PS: 要轉載請注明出處,本人版權所有。
PS: 有問題請留言,看到后我會第一時間回復。