사각형을 찾도록 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
테스트 결과 회전한 사각형을 잡아내긴 하지만 사각 영역으로 알려주는 단점이 있네요.
원본 이미지와 결과 이미지입니다.
'OpenCV > OpenCV 강좌' 카테고리의 다른 글
OpenCV Python - Image Center Crop 예제 (0) | 2022.03.06 |
---|---|
Python OpenCV에서 이미지 크기 (width, height) 가져오기 (2) | 2021.11.14 |
해리스 코너를 사용한 이미지 매칭(Image feature matching with Harris Corner Detection) (8) | 2021.08.01 |
OpenPose를 사용하여 손가락 인식하는 OpenCV 예제 (0) | 2021.02.02 |
char 배열과 Mat간 변환하는 OpenCV 예제 (0) | 2020.12.10 |
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!