적당한 고통은 희열이다

- 댄 브라운 '다빈치 코드' 중에서

Swift iOS 앱 개발/iOS

[Objective-C iOS] OpenCV detect rectangle 윤곽선 가져오기

hongssup_ 2022. 1. 12. 17:58
반응형

 

내가 서치해본 결과, iOS에서 OpenCV를 사용하기엔 굉장히 열악하다. 

샘플 코드 발견해도 대부분 4, 5년 전에 작성되어 작동조차 안되는 경우가 많다..

Swift로 작성할수가 없으니 Swift opencv 검색하면 당연히 안나온다. 그냥 기초 흑백변환 가이드 정도만 있다.

OpenCV 를 활용하고 싶다면 무조건 Objective-C로 작성을 해주어야 하고, 옵씨로 검색을 해야한다. 

그나마 파이썬이나 안드로이드에서 사용된 예시는 꽤 있는 듯 하여 작동 흐름이라도 알기 위해 다른 분야 검색 결과들을 참고 했다.

인자 갯수나 사용법은 조금씩 다르지만 사용되는 함수 명이나 흐름은 비슷하니까 다른 언어로 참고해도 좋을듯! 

의도치않게 Objective-C 공부도 하게된.. ㅎㅎㅎ 

그리하여 내가 직접 정리해본 iOS OpenCV 사용 가이드! 

* ios opencv 설치 및 프로젝트 설정에 대한 자세한 설명은 👉🏻여기를 참고하세욤

 

OpenCV - 윤곽선 가져오기

1. 흑백 변환 

OpenCV에서 이미지 작업을 해주기 전에 우선 흑백, Grayscale 로 바꿔준다. 

cv::Mat imageMat;
UIImageToMat(image, imageMat);
//Convert to gray
cv::Mat grayMat;
cv::cvtColor(imageMat, grayMat, cv::COLOR_BGR2GRAY);

 

2. 임계값 설정 및 바이너리 이미지 변환

다음 단계에서 사용할 findContours 메소드는 '바이너리 이미지'에서 윤곽선을 찾아주기 때문에, 먼저 Grayscale 이미지를 바이너리로 바꿔주는 과정이 필요하다.

binary image : 흑과 백 두 가지 색상 중 하나를 가질 수 있는 픽셀로 구성된, 비트맵 이미지

cv::Mat threshold_output;
cv::threshold(grayMat, threshold_output, 130.0, 255.0, cv::THRESH_BINARY);

위와 같이 threshold를 사용해줄 수 있는데, 인자는 순서대로 inputArray image, outputArray image, double thresh, double maxval, int type 이다. 

여기서 주목해야할 부분은 세번째 thresh 인자, 바로 임계값이다. 

threshold

: 이미지를 흑백 바이너리 이미지로 변환하기 위해 설정하는 임계값

Grayscale 이미지는 0 ~255 까지의 색상값이 있는데, 이를 이진화해주기 위해 임계값을 설정해주어야 한다. 

색상 값이 임계값 보다 작으면 검정색으로 변하고, 임계값보다 높으면 흰색으로 변한다. 이를 Global Threshold 라고도 한다. 

참고 : github.io - 글자 인식(파이썬, OpenCV)

임계값에 따라 결과값이 어떻게 달라지는지 영수증 사진으로 임계값을 비교해 보았다. 

왼쪽이 임계값 170일 때, 오른쪽이 임계값 130일 때

우와 신기신기 >< 

임계값을 170으로 해줄 때 보다 130으로 설정해주니 확실히 더 깔끔하게 변환이 되는 것을 확인할 수 있었다. 

그림자진 부분의 색상값이 아마도 130~170 사이였나봄. 역시 실제로 해보니까 임계값이 뭔지 확실이 이해가 된다. 

사진마다 최적의 임계값이 조금씩 다른 것 같던데, 사진마다 다른 임계값을 찾아주는 기능도 있나 한번 찾아보아야겠다.. 

 

그리고 바이너리 이미지로 변환을 해줄거기 때문에 마지막 type 인자는 binary로 설정해준다. 

타입에 들어갈 THRESH_BINARY 와 THRESH_BINARY_INV 의 차이는 다음과 같다. 

흰 배경에 까만 글자를 원한다면 기본 바이너리 타입을, 검은 배경에 흰 글자를 원하면 인버스 타입을 사용하면 될 것 같다. 

 

3. 윤곽선 찾기

findContours

: Find contours in a binary image

std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(threshold_output, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())

윤곽선 찾기 findCountours

원본 이미지를 바꾸어버리기 때문에, 원본을 보존하고 싶다면 변수를 새로 만들것? 

mode : 윤곽선 검출 모드 contour retrieval mode

• CV_RETR_EXTERNAL : 객체의 바깥 윤곽선만 검출. 계층정보 X. retrieves only the extreme outer contours

• CV_RETR_LIST : 모든 윤곽선을 계층정보 없이 가져옴 retrives all of the contours without establishing any hierarchical relationships

• CV_RETR_CCOMP : 모든 윤곽선을 검색하여 2단계 계층 구조로 구성 retrieves all of the contours and organizes them into a two-level hierarchy. 

• CV_RETR_TREE : 모든 윤곽선과 윤곽선 내에 포함된 또다른 윤곽선의 전체 계층정보를 재구성. retrieves all of the contours and reconstructs a full hierarchy of nested contours. 

method : 윤곽선 근사화 방법 contour approximation method

• CHAIN_APPRPX_NONE : stores absolutely all the contour points 근사화 없이 0모든 경계점 저장.

but 선을 표현하기 위해서는 모든 점이 아니라 양 끝 두 점만 있으면 된다. 

따라서 CHAIN_APPROX_SIMPLE 을 통해 최소한의 점으로 표현하여 countour를 압축하고 메모리를 절약한다. 

• 

참고 : 찰스의 안드로이드 - OpenCV 윤곽선 검출하기

 

4. 윤곽선 그리기

drawContours

: Draws contours outlines or filled contours 

cv::drawContours(threshold_output, contours, i, cv::Scalar(0,255,0), 10);

void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )

 

 

approxPolyDP

Approximates a polygonal curve(s) with the specified precision

cv::approxPolyDP(contours[i], contours[i], epsilon, true);

void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)

 

 

+ (UIImage *)getContours:(UIImage *)image {
    cv::Mat imageMat;
    UIImageToMat(image, imageMat);
    //Convert to gray
    cv::Mat grayMat;
    cv::cvtColor(imageMat, grayMat, cv::COLOR_BGR2GRAY);
    //Detect edges using Threshold
    cv::Mat threshold_output;
    cv::threshold(grayMat, threshold_output, 190.0, 255.0, cv::THRESH_BINARY);
    //Find Contours to find chains of consecutive edge pixels
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(threshold_output, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    
    for (int i = 0; i < contours.size(); i++) {
        double epsilon = cv::arcLength(contours[i], true) * 0.02;
        //NSLog(@"epsilon: %f", epsilon);
        cv::approxPolyDP(contours[i], contours[i], epsilon, true);
        
        if (100000 < contourArea(contours[i]) && contourArea(contours[i]) < 600000) {
            NSLog(@"contourArea: %f", contourArea(contours[i]));
            //Draw contours on image
            cv::drawContours(threshold_output, contours, i, cv::Scalar(0,255,0), 10);
        }
    }
    return MatToUIImage(threshold_output);
}

 

* 주의사항 

끝에 ; 붙여주기

.mm 파일에 함수 만들어주고 나면 .h 에도 반드시 선언해주기. 

 

 

문제가 있다. 

drawContours에서 Scalar로 윤곽선 색상을 설정해주는데도, 색깔없이 그냥 검정색으로만 그려진다. 왜지?

 

OpenCV 윤곽선 가져오기 샘플 프로젝트는 👉🏻여기를 참고해주세요.


참고 : StackOverflow - Square detectionOpenCV doc - Image processing shape detection,

 

혹시나 제 글이 도움이 되셨다면 하트 한번 눌러주시면 감사하겠습니다 🥰

iOS 개발자분들 모두 화이팅입니다👍🏻



728x90
반응형