一種實用性較強的求IOU的算法(任意多邊形之間的IOU)


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}\)

  下面我們用一個示例圖清晰的表達這個概念:

rep_img

  其中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的數值不處理的話,不是那么可愛的。

  其實這里的幾種用法還可以延伸出來,比如算兩個立方體的重合度等等。

參考文獻




打賞、訂閱、收藏、丟香蕉、硬幣,請關注公眾號(攻城獅的搬磚之路)
qrc_img

PS: 請尊重原創,不喜勿噴。

PS: 要轉載請注明出處,本人版權所有。

PS: 有問題請留言,看到后我會第一時間回復。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM