반응형

사각형을 찾도록 MSER(Maximally Stable Extremal Regions)을 이미지에 적용한 다음  IoU(Intersection over Unit)를 사용하여 같은 영역에 중복되어 그려진 사각형을 제거해보았습니다. 



테스트한 이미지에서만 사용해본 코드라 다른 이미지에서는 의도한대로 동작하지 않기 때문에 추가 작업이 필요합니다.  

포스트에선 IOU값이 0.2보다 크면 사각형 내부에 사각형이 포함된 것으로 보았는데 두 사각형이 포함관계가 아니라 일부 겹쳐진 경우도 해당 될 수 있기 때문입니다.  



포스트에서는 문제를 간단하게 하기 위해서 다음 이미지에서 검출되는 5개의 사각형 중, 사각형 내에 있는 작은 사각형을 제외하고 나머지 사각형을 검출하도록 했습니다. 

 

 

2021. 9. 23 - 최초작성

2021. 10. 4 - 추가 테스트




실행하면 다음처럼 검출된 사각형 5개를 순서대로 보여줍니다. 검출된 사각형에 형광 녹색 사각형을 그린 다음 인덱스 숫자를 출력했습니다. 

 



2개씩 짝지어서 IOU를 계산합니다. 사각형이 서로 겹치지 않는 영역에 있다면 IOU 값은 0입니다. 

사각형 인덱스 2개를 -로 짝지어 출력한 후, 뒤에 IOU 값을 출력하도록 했습니다. 

 

  1-0 0.306074

 

2-0 0.000000

 

2-1 0.000000

 

3-0 0.000000

 

3-1 0.000000

 

3-2 0.000000

 

4-0 0.000000

 

4-1 0.000000

 

4-2 0.436677

 

4-3 0.000000




최종 검출 결과입니다. 내부 사각형을 제외한 나머지 사각형들만 형광 녹색 사각형으로 표시되어 있습니다. 

 



터미널에 출력된 결과에서 인덱스 1, 2번인 사각형은 제거되고 나머지 0, 3, 4번 사각형만 남은것을 볼 수 있습니다. 

 

delete 1

delete 2

show 0

show 3

show 4




전체 코드입니다. 

 

#include <iostream>
#include <numeric>
#include <opencv2/opencv.hpp>


using namespace std;
using namespace cv;


/*
- 출처 - https://www.section.io/engineering-education/graphs-in-data-structure-using-cplusplus/
*/
class graph{
public:
    vector<int> *adjlist;
    vector<vector<int>> result;
    int n;
    graph(int v){
        adjlist=new vector<int> [v];
        n=v;
    }

    void addedge(int u,int v,bool bi){
        adjlist[u].push_back(v);
        if(bi){
            adjlist[v].push_back(u);
        }
    }

    vector<vector<int>> print(){
       
        for(int i=0;i<n;i++){

            vector<int> vl;
            vl.push_back(i);
            //cout<<i<<"-->";
            for(auto it:adjlist[i]){
                //cout<<it<<" ";
                vl.push_back(it);
            }
            //cout<<endl;

            if (vl.size() > 1)
                result.push_back(vl);
        }
        //cout<<endl;

        return result;
    }


};


/* 출처 - //https://gaussian37.github.io/math-algorithm-iou/
- Rectangle A : (min_x1, min_y1), (max_x1, max_y1)
- Rectangle B : (min_x2, min_y2), (max_x2, max_y2)
- Intersection : A ∩ B
- Union : A + B - A ∩ B
- IoU : (A ∩ B) / (A + B - A ∩ B)
*/
double RectangleIoU(int min_x1, int min_y1, int max_x1, int max_y1, int min_x2, int min_y2, int max_x2, int max_y2) {

    double ret;
    double rect1_area, rect2_area;
    double intersection_x_length, intersection_y_length;
    double intersection_area;
    double union_area;

    // 직사각형 A, B의 넓이를 구한다.
    // get area of rectangle A and B
    rect1_area = (max_x1 - min_x1) * (max_y1 - min_y1);
    rect2_area = (max_x2 - min_x2) * (max_y2 - min_y2);

    // Intersection의 가로와 세로 길이를 구한다.
    // get length and width of intersection.
    intersection_x_length = MIN(max_x1, max_x2) - MAX(min_x1, min_x2);
    intersection_y_length = MIN(max_y1, max_y2) - MAX(min_y1, min_y2);

    // width와 length의 길이가 유효하다면 IoU를 구한다.
    // If the width and length are valid, get IoU.
    if (intersection_x_length > 0 && intersection_y_length > 0) {
        intersection_area = intersection_x_length * intersection_y_length;
        union_area = rect1_area + rect2_area - intersection_area;
        ret = intersection_area / union_area;
    }
    else {
        ret = 0;
    }

    return ret;
}


int main(int argc, char** argv)
{
    // if (argc != 2)
    // {
    //     printf("Usage : %s image_file\n", argv[0]);
    //     exit(0);
    // }

    //Mat img = imread(argv[1]);
    Mat img = imread("test.png");
    int image_height = img.rows;
    int image_width = img.cols;

    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);



    // MSER을 사용하여 사각형을 검출합니다.
    Ptr<MSER> mser = MSER::create();
    vector<vector<Point>> regions;
    vector<Rect> mser_bbox;
    mser->detectRegions(gray, regions, mser_bbox);
   
    
    // 검출된 사각형 개수입니다.
    int total = mser_bbox.size();

    printf("mser_bbox.size() %d\n", total);



    // 검출된 사각형에 하나씩 형광 녹색 사각형과 인덱스를 출력합니다.
    for (int j = 0; j < mser_bbox.size(); j++)
    {
        Mat temp = img.clone();


        string Text = std::to_string(j);

        cv::Point mPoint;
        mPoint.x = mser_bbox[j].x + 100;
        mPoint.y = mser_bbox[j].y + 100;
   
        int FontFace = 2;

        double FontScale = 1.2;
   
        cv::putText( temp, Text, mPoint, FontFace, FontScale, Scalar(0, 0, 0) );


        rectangle(temp, mser_bbox[j], CV_RGB(0, 255, 0), 3);

        imshow("element", temp);
        waitKey(0);
    }



    // 검출된 사각형 인덱스를 벡터 box_list에 저장합니다.
    // 나중에 여기서 사각형 내에 있는 사각형의 인덱스만 제거하게 됩니다.
    vector<int> box_list;
    for (int i = 0; i < mser_bbox.size(); i++)
    {
        box_list.push_back(i);
    }



    // IOU값이 0.2 이상인 경우 사각형 내에 사각형이 포함된 것으로 보고 이 둘을 그래프 에지로 저장합니다.
    // IOU값이 0보다 크면 두 사각형이 약간이라도 겹쳐진 것입니다. 내부에 사각형이 포함된 상태가 아닐 수도 있습니다.    
    graph g(total);

    for (int i = 0; i < mser_bbox.size(); i++)
    {
        int min_x1 = mser_bbox[i].x;
        int min_y1 = mser_bbox[i].y;
        int max_x1 = mser_bbox[i].x + mser_bbox[i].width;
        int max_y1 = mser_bbox[i].y + mser_bbox[i].height;



        for (int j = 0; j < mser_bbox.size(); j++)
        {
            if (i == j) break;

            int min_x2 = mser_bbox[j].x;
            int min_y2 = mser_bbox[j].y;
            int max_x2 = mser_bbox[j].x + mser_bbox[j].width;
            int max_y2 = mser_bbox[j].y + mser_bbox[j].height;

            Mat temp = img.clone();

            rectangle(temp, mser_bbox[i], CV_RGB(0, 255, 0), 3);

            rectangle(temp, mser_bbox[j], CV_RGB(0, 255, 0), 3);

            double iou = RectangleIoU(min_x1, min_y1, max_x1, max_y1, min_x2, min_y2, max_x2, max_y2);

            if (iou > 0.2)
                g.addedge(i,j,true);

            printf("%d-%d %lf\n", i, j, iou);

            imshow("detect", temp);
            waitKey(0);

        }
    }


    // 벡터 형태로 그래프 에지를 리턴받습니다. 같은 벡터에 포함되어 있다면 영역일부가 서로 겹처진 사각형입니다.
    vector<vector<int>> result = g.print();

    for (int i=0; i<result.size(); i++)
    {
        sort(result[i].begin(), result[i].end());
    }



    // set에 삽입하여 중복 벡터를 제거합니다.
    set<vector<int>> s;

    for (int i=0; i<result.size(); i++)
    {
        auto ret = s.insert(result[i]);
    }



    // 최종적으로 영역이 겹친 사각형이 저장된 벡터 개수입니다.
    printf("final %d\n", s.size());



    // 같은 벡터에 있는 사각형중 제일 큰것만 남기고 나머지 사각형의 인덱스는 제거 후보 will_removed에 저장합니다.
    vector<int> will_removed;

    for(vector<int> elem : s)
    {

        int max_area = -1;
        int max_index = -1;

        vector<int> v = elem;

        for (int i=0; i<v.size(); i++)
        {
            int idx = v[i];

            int min_x1 = mser_bbox[idx].x;
            int min_y1 = mser_bbox[idx].y;
            int max_x1 = mser_bbox[idx].x + mser_bbox[idx].width;
            int max_y1 = mser_bbox[idx].y + mser_bbox[idx].height;

            int rect1_area = (max_x1 - min_x1) * (max_y1 - min_y1);

            if ( rect1_area > max_area)
            {
                max_index = idx;
                max_area = rect1_area;
            }
        }

        for (int i=0; i<v.size(); i++)
        {
            int idx = v[i];
            if (idx != max_index)
                will_removed.push_back(idx);
        }
    }


    // 전체 사각형이 저장된 벡터 box_list에서 제거 대상 후보 will_remove의 원소를 제거합니다.   
    for( int i=0; i<will_removed.size(); i++ )
    {
        printf("delete %d\n", will_removed[i]);

        box_list.erase(std::remove(box_list.begin(), box_list.end(), will_removed[i]),box_list.end());
    }



    // 최종적으로 내부에 사각형이 포함되지 않은 사각형에만 초록색 사각형을 그려주고 화면에 출력합니다. 
    Mat temp = img.clone();

    for( int i=0; i<box_list.size(); i++ )
    {
        int idx = box_list[i];
        printf("show %d\n", idx);

        rectangle(temp, mser_bbox[idx], CV_RGB(0, 255, 0), 3);
    }

    imshow("final", temp);
    waitKey(0);

}



2021. 10. 4 

테스트 결과 회전한 사각형을 잡아내긴 하지만 사각 영역으로 알려주는 단점이 있네요.

원본 이미지와 결과 이미지입니다.

 

 




반응형

해보고 확인한 것을 문서화하여 기록합니다.


부족함이 있지만 도움이 되었으면 합니다.
잘못된 부분이나 개선점을 알려주시면 감사하겠습니다.



포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
문제가 생기면 포스트와 바뀐 환경이 있나 먼저 확인해보세요.

질문을 남겨주면 가능한 빨리 답변드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

">