반응형

이진화시킨 이미지에서 findContours함수를 사용하여 contour를 찾은 후,  approxPolyDP 함수를 사용하여 다각형(polygon)을 검출하는 예제입니다.




OpenCV 사용해서 실시간으로 도형 검출하기(shape detection) 1 / 2 -  이미지에서 검출


OpenCV 사용해서 실시간으로 도형 검출하기(shape detection) 2 / 2 -  웹캠에서 검출

http://webnautes.tistory.com/1193



2016. 12. 29   최초 작성

2018.   6. 30 도형 판정하는 방법 변경(  내각 체크 → Convex polygon 여부 검사 )

                      webcam으로 테스트 추가




1-1. 다음 테스트용 이미지를 저장하여 OpenCV를 위한 프로젝트 폴더에 넣습니다.


사각형, 육각형, 십각형 도형에는 각각 해당 도형은 아니지만 꼭지점 갯수가 같은 도형이 같이 추가되어 있습니다.

원의 경우에는 팔각형이 추가되어 있는데 실행 결과에서 이유를 설명합니다.




Visual Studio 2017의 경우 프로젝트 이름이 OpenCV Project V 라면 이미지를 복사해놓을 위치는 다음과 같습니다.

C:\Users\로그인_사용자_이름\source\repos\OpenCV Project V\OpenCV Project V




OpenCV를 위한 프로젝트 생성 방법은 다음 포스팅을 참고하세요.


Visual Studio 2017에서 OpenCV 3.4.1를 사용하는 방법

http://webnautes.tistory.com/1132





1-2. 다음 소스코드를 프로젝트의 cpp 소스 파일에 붙여넣기 합니다.


#include "opencv2/opencv.hpp"
#include <iostream>  
#include <string>

using namespace cv;
using namespace std;



//Contour 영역 내에 텍스트 쓰기
//https://github.com/bsdnoobz/opencv-code/blob/master/shape-detect.cpp
void setLabel(Mat& image, string str, vector<Point> contour)
{
int fontface = FONT_HERSHEY_SIMPLEX;
double scale = 0.5;
int thickness = 1;
int baseline = 0;

Size text = getTextSize(str, fontface, scale, thickness, &baseline);
Rect r = boundingRect(contour);

Point pt(r.x + ((r.width - text.width) / 2), r.y + ((r.height + text.height) / 2));
rectangle(image, pt + Point(0, baseline), pt + Point(text.width, -text.height), CV_RGB(200, 200, 200), FILLED);
putText(image, str, pt, fontface, scale, CV_RGB(0, 0, 0), thickness, 8);
}



int main(int, char**)
{
Mat img_input, img_result, img_gray;

//이미지파일을 로드하여 image에 저장  
img_input = imread("test.png", IMREAD_COLOR);
if (img_input.empty())
{
cout << "Could not open or find the image" << std::endl;
return -1;
}


//그레이스케일 이미지로 변환  
cvtColor(img_input, img_gray, COLOR_BGR2GRAY);

//이진화 이미지로 변환
Mat binary_image;
threshold(img_gray, img_gray, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

//contour를 찾는다.
vector<vector<Point> > contours;
findContours(img_gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);

//contour를 근사화한다.
vector<Point2f> approx;
img_result = img_input.clone();

for (size_t i = 0; i < contours.size(); i++)
{
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

if (fabs(contourArea(Mat(approx))) > 100)  //면적이 일정크기 이상이어야 한다.
{

int size = approx.size();

//Contour를 근사화한 직선을 그린다.
if (size % 2 == 0) {
line(img_result, approx[0], approx[approx.size() - 1], Scalar(0, 255, 0), 3);

for (int k = 0; k < size - 1; k++)
line(img_result, approx[k], approx[k + 1], Scalar(0, 255, 0), 3);

for (int k = 0; k < size; k++)
circle(img_result, approx[k], 3, Scalar(0, 0, 255));
}
else {
line(img_result, approx[0], approx[approx.size() - 1], Scalar(0, 255, 0), 3);

for (int k = 0; k < size - 1; k++)
line(img_result, approx[k], approx[k + 1], Scalar(0, 255, 0), 3);

for (int k = 0; k < size; k++)
circle(img_result, approx[k], 3, Scalar(0, 0, 255));
}



//도형을 판정한다.
if (size == 3)
setLabel(img_result, "triangle", contours[i]); //삼각형

//이하는 해당 꼭지점을 가진 convex라면 찾는 도형
else if (size == 4 && isContourConvex(Mat(approx)))
setLabel(img_result, "rectangle", contours[i]); //사각형

else if (size == 5 && isContourConvex(Mat(approx)))
setLabel(img_result, "pentagon", contours[i]); //오각형

else if (size == 6 && isContourConvex(Mat(approx)))
setLabel(img_result, "hexagon", contours[i]);  //육각형

else if (size == 10 && isContourConvex(Mat(approx)))
setLabel(img_result, "decagon", contours[i]);    //십각형

//위 조건에 해당 안되는 경우는 찾아낸 꼭지점 갯수를 표시
else setLabel(img_result, to_string(approx.size()), contours[i]);
}

}


imshow("input", img_input);
imshow("result", img_result);


waitKey(0);


return 0;
}





1-3. 실행해보면 원본 이미지를 보여주는 input 창과 도형에 인식된 이름을 출력해주는 result 창이 보여집니다.


결과 창입니다. 테스트 이미지에 주어진 모든 도형이 회전 되었는지 여부랑 상관없이 해당 도형을 검출하고 있습니다. 꼭지점 갯수를 기반으로 해당 도형인지 판단 하기 때문입니다.



해당 다각형을 위한 꼭지점 갯수를 가지고 있더라도 Convex polygon이 아니라면 제외 시켰기 때문에 파란색 2번, 녹색 2번, 노란색 2번은 검출된 이름 대신에 꼭지점 갯수만 출력하고 있습니다.


원에서 검출된 contour를 approxPolyDP 함수로 근사화하면 8개의 꼭지점을 갖습니다.

필요시 팔각형과 원 중 한쪽으로 인식이 완료되었다고 코드를 수정해도 될 듯합니다.


또는 OpenCV에서 제공하는 원을 검출하는 HoughCircles 함수를 사용해도 됩니다.

https://docs.opencv.org/3.4/d4/d70/tutorial_hough_circle.html



다음 포스팅에서는  웹캠을 사용하여 실시간으로 도형 검출하는 것을 진행해보겠습니다.



반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
질문을 남겨주면 가능한 빨리 답변드립니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 ~~

유튜브 구독하기


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

  1. 2017.01.18 16:51

    이것은 안드로이드용이 아닌가요?ㅠ

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2017.01.18 17:00 신고

      c++로 구현된건 모두 안드로이드용으로 가능합니다.

    • 2017.01.18 17:06

      MainActivity나 activity_main.xml 소스가 안올라와있어서 어떻게 써야할지 감이 안잡히네요.. ㅎㅎ
      OpenCV + NDK Android 이미지 로드하여 영상처리하는 예제
      이 예제에있던 소스에서 cpp파일만 위대로 바꿔주니 에러가 떠서요..

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2017.01.18 17:29 신고

      기존 코드를 참고하여 C++ 파일과 JAVA 파일로 나누어야 합니다.. 기존 코드가 어떻게 짜여져있는지 잘 보셔야 됩니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2017.01.18 19:33 신고

      안드로이드 스튜디오에서 어떻게 설정하는지 모르겠네요

  2. 2017.01.21 11:32

    이거 안드로이드 버전으로 face detection처럼 모양 인식 예제 올려주시면 안되나요?...

    혼자 해보려니 며칠째 빡세네요..

  3. Favicon of https://poorman.tistory.com BlogIcon 푸어맨 2017.02.08 00:16 신고

    소스 코드를 알기 쉽게 짜주셔서 도움이 많이 되었습니다. 블로그 통해 많이 배웁니다. 감사합니다.
    코드 참고해서 각 코너의 각도를 더한 합을 이용하여 도형을 구분하는 방식으로 정다각형이 아닌 경우를 검출하도록 변경해봤습니다.
    혹시 다른 개선 방안이나 본 방법의 문제점이 있다면 가르침 부탁드립니다.

    int ang_sum = 0;

    cout << "===" << size << endl;
    for (int k = 0; k < size; k++) {
    int ang = GetAngleABC(approx[k], approx[(k + 1) % size], approx[(k + 2) % size]);
    cout << k << k + 1 << k + 2 << "@@" << ang << endl;

    ang_sum += ang;
    }

    int ang_threshold = 8;
    int ang_sum_min = (180 - ang_threshold) * (size - 2);
    int ang_sum_max = (180 + ang_threshold) * (size - 2);
    //도형을 판정한다.
    if (size == 3)
    setLabel(img_result, "triangle", contours[i]);
    else if (size == 4 && ang_sum >= ang_sum_min && ang_sum <= ang_sum_max)
    setLabel(img_result, "rectangle", contours[i]);
    else if (size == 5 && ang_sum >= ang_sum_min && ang_sum <= ang_sum_max)
    setLabel(img_result, "pentagon", contours[i]);
    else if (size == 6 && ang_sum >= ang_sum_min && ang_sum <= ang_sum_max)
    setLabel(img_result, "hexagon", contours[i]);
    else setLabel(img_result, to_string(approx.size()), contours[i]);//알수 없는 경우에는 찾아낸 꼭지점 갯수를 표시

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2017.02.08 10:18 신고

      알려주신 다각형의 내각의 합을 이용하는 것도 좋은 방법인거 같습니다. 공유해주셔서 감사합니다.

      제 방법으로는 정다각형만 확인이 가능한데 알려주신 방법으로는 변형된 평행사변형도 사각형으로 인식해서 좋은듯 싶습니다.

      개선방안이 있다면 다각형간에 내각의 합차이가 크니 threshold를 아래처럼 하면 어떨까 싶네요..

      int ang_sum_min = (180 * (size - 2))-ang_threshold;
      int ang_sum_max = (180 * (size - 2))+ang_threshold;

      그리고 이번에 추가로 태스트하다가 안점인데 ㄱ모양이나 별모양의 경우 일부 내각이 마이너스로 나옵니다. 이것에 대한 처리만 추가해주면 좋을듯합니다.

  4. 초보 2017.05.22 21:45

    approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);
    이계 무슨 함수인가요?
    비주얼 스튜디오로 돌려보니까 여기서 오류가 발생합니다 혹시 opencv2.8에서는 안되는 건가요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2017.05.22 23:26 신고

      물체의 외곽선인 컨투어를 근사화하여 직선으로 바꾸기 위해서 사용되는 함수입니다.

      OpenCV 2.x 버전에서도 사용가능한 함수로 알고 있습니다..

      입력 이미지를 혹시 바꿔서 에러난건 아닌지 확인해보세요..

      입력 이미지에 따라 인자를 바꾸지 않으면 에러날 수 도 있습니다.

  5. 2017.07.21 11:23

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2017.07.21 21:27 신고

      이후 더 정보가 필요하시면 OpenCV 홈페이지의 튜토리얼을 확인해보세요.

      파이썬용과 C/C++용 모두 보시는게 좋습니다.
      OpenCV Tutorials
      OpenCV-Python Tutorials

  6. 조은샘 2018.06.21 14:52

    혹시 이예제 캠으로 변환도 가능한가요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.06.21 18:36 신고

      해당 도형들을 프린트해서 테스트해보실 생각인가요?

      저도 해볼까 했는데
      아직 테스트는 못해봤습니다..

      이미지로 하는 것과는 상황이 다르기 때문에 정확도는 떨어질 듯합니다.

      추가 작업이 더 필요할 수 있습니다.



  7. 개리씌 2018.07.11 16:58

    저는 stop표지판 인식하는 프로그램을 하고있습니다. 그래서 팔각형만 검출하려 하는데 혹시 approx라는 꼭지점 배열에서 어느 지점을 처음으로 찾는 지점 즉 approx[0]이 정해져있나요?? 아니면 매번 달라지나요?? 팔각형의 가로길이와 세로길이를 따내야 해서요

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.07.11 18:37 신고

      정렬을 해줘야합니다.

      시계방향 혹은 반시계방향이 될수 있고

      물어보신 것의 인덱스 0의 위치도 테스트 할때마다 달라집니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.07.11 18:40 신고

      다음 포스팅이 도움이 되지 않을까 싶네요.

      ArUco Marker Detection 구현 및 Pose Estimation

      http://webnautes.tistory.com/1040

  8. david 2019.01.09 14:09

    안녕하세요 궁금한 것이 있어 질문 드립니다.

    지금 올려주신 예제에서 원 모양을 검출할 때 아웃라인이 원이 아닌 8각형 모형으로 잡아낸 것을 확인했습니다.

    혹시 다른 모형들은 저대로 내비두고 원형 모형에서만 이 8각형 라인을 제거하고 원형 아웃라인으로 잡아내고 싶다면 어떻게 진행해야 할까요?

    원형 잡아내는 것은 아래 코드를 통해서 확인하였습니다.

    //원 검출
    vector <Vec3f> circles;
    HoughCircles(img_gray, circles, CV_HOUGH_GRADIENT, 1, 20, 200, 50, 0, 0);

    for (size_t j = 0; j < circles.size(); j++) {
    Vec3i c = circles[j];
    Point center = Point(c[0], c[1]);

    circle(img_result, center, 2, Scalar(0, 255, 0), 2, LINE_AA);

    int radius = c[2];

    circle(img_result, center, radius, Scalar(0, 255, 0), 2, LINE_AA);

    }

    위에 포스팅해주신 것과 저 코드를 합쳐서 하나의 코드로 만들고 싶은데 혹시 어찌 해야할까요?

    원형 모양은 꼭지점이 0개라고 생각하여 contour를 근사화한 직선을 그리는 부분인

    if (size % 2 == 0){
    이 부분을 참고하여 size == 0 일 때 저 위에 작성한 for문을 넣어주고 돌려본 결과 원형 아웃라인은 나타나지 않는 결과를 얻게 되었습니다.

    혹시 어떤 식으로 합쳐야 사각형, 삼각형을 제외한 원형에서는 꼭지점과 직선형태의 라인이 나타나지 않고, 원을 인식하는 원형 아웃라인만 나타나게 할 수 있을까요?

    너무 장황하게 작성하여 죄송합니다.

    답변 부탁드리겠습니다.

    감사합니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.01.09 14:17 신고

      라벨링을 사용하여 도형들을 작은 이미지(ROI)로 나눈 후 각각 다각형인지 원인지 확인하는 방법이 좋을 듯합니다.

      라벨링은 다음 포스팅을 참고하세요.

      openCV 라벨링 예제 ( connectedComponentsWithStats )
      https://webnautes.tistory.com/823

  9. david 2019.01.09 18:30

    답변 감사합니다.

    혹시 저 코드에서 꼭지점 개수를 표현하는 것이 approx.size() 이것 맞나요?

    그리고 원일 때는 꼭지점 개수가 몇 개인지 확인할 때

    원에서 검출된 contour를 approxPolyDP 함수로 근사화하면 8개의 꼭지점을 갖습니다.
    필요시 팔각형과 원 중 한쪽으로 인식이 완료되었다고 코드를 수정해도 될 듯합니다.

    이렇게 말씀하셨는데 그렇다면 저 함수에서 원을 제외시키는 방법이 있을까요?

    현재 제가 작성한 것은 원을 detecting 하는 houghcircle 함수를 사용하여 원을 검출하고 있습니다.

    그런데 approxPolyDP 이 함수를 사용하니 두 번 검출되는 효과가 나타나게 되네요.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.01.09 18:33 신고

      if (size % 2 == 0 && size != 8 ) {로 제외시켜서 컨투어를 그리지 않으면 될듯합니다.

  10. david 2019.01.10 10:02

    답변 정말 감사합니다. 해결했습니다 ~~

  11. 질문있는학생 2019.01.15 16:34

    CV_FILLED가 정의되어있지 않다고하는데 뭐가 문제일까요..?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.01.15 16:51 신고

      포스팅 작성시 C API에 있는 상수를 사용해서 발생한 문제입니다.

      OpenCV 4.0.0부터 C API가 제거되어 에러가 발생합니다.

      CV_FILLED를 FILLED로 변경하면 해결됩니다.

  12. 제이케이 2019.12.07 20:52

    안녕하세요 작성자님
    opencv3.0 으로 실행했는데 to_string 오버로드된 함수에 대한 호출이 모호하다고 오류가 납니다
    어떤게 문제인지 혹시 알고 계신다면 알려주실 수 있을까요..?

  13. 제발 2019.12.10 22:51

    안녕하세요
    else setLabel(img_result, to_string(approx.size()), contours[i]); 이 부분에서 to_string가 오버로드된 함수 "to_string"의 인스턴스 중 두 개 이상이 인수 목록과 일치합니다라고 뜹니다.. 어떻게 해야하나요?
    opencv 3.0으로 실행했습니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.12.11 09:12 신고

      이상하군요.. c++용 함수인데..

      다음처럼 바꿔도 변화가 없으면 sprintf를 사용해보세요.

      string str = to_string(approx.size());
      else setLabel(img_result, str, contours[i]);

  14. Zero 2020.01.29 12:31

    안녕하세요 opencv를 공부시작하고 난 뒤로 항상 잘보고 있습니다. 감사합니다
    위에 코드를 그대로 붙여넣은 뒤 사진을 읽어드리는 위치만 변경해서 돌려봤는데
    vector<vector<Point> > contours;
    findContours(img_gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
    이 부분에서
    "0x00007FFCC5BDC4C8(ucrtbase.dll)에(OpenCV_TEST.exe의) 처리되지 않은 예외가 있습니다. 잘못된 매개 변수를 심각한 오류로 간주하는 함수에 잘못된 매개 변수가 전달되었습니다.."
    라고 오류가 뜨는데 혹시 왜 뜨는 걸까요....??
    그리고 어떻게 해결을 해야되는지 가르켜 주실 수 있으실까요??

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.01.29 23:25 신고

      코드 문제는 아닌듯합니다. 테스트해본 Visual Studio 2017 + OpenCV 4.1.2에서 문제없이 실행됩니다.

  15. Zero 2020.01.30 12:11

    아 감사합니다.
    Visual Studio 2017 + OpenCV 3.2.0으로 사용중인데 이 차이 때문에 오류가 발생했나 보내요
    어떻게 변환 가능한지를 한번 찾아보겠습니다

  16. ggamdi 2020.03.06 00:43

    안녕하세요 opencv 공부하면서 정말 많은 도움이 되고있습니다

    한가지 궁금한게 있는데 경계가 모호한 부분은 어떻게 구분을해서 나눠야할지 모르겠습니다

    검은테두리가 있던지 이러면 잘 인식이되는데 색이 조금비슷하거나 이러면 아예 구별을 못해서 혹시 이부분에 대해서 도움 주실수 있으신가요??

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.03.06 09:00 신고

      이진화하는 부분의 결과를 확인해보고 원하는 결과가 나오도록 파라미터 또는 방법을 변경해보세요.

      threshold(img_gray, img_gray, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

+ Recent posts