본문 바로가기
프로그래밍/OpenCV

[OpenCV] 직선, 도형, 문자 그리기

by 별준 2022. 5. 4.

References

Contents

  • 직선 그리기
  • 도형 그리기
  • 문자열 출력하기

이번 포스팅에서는 OpenCV에서 제공하는 다양한 그리기 함수에 대해 살펴보겠습니다.

직선 그리기

가장 먼저 살펴볼 그리기 함수는 영상 위에 직선을 그리는 line() 함수입니다. 이 함수의 원형은 다음과 같습니다.

그리고 전달되는 파라미터 정보는 다음과 같습니다.

line() 함수는 img 영상 위에 pt1 좌표부터 pt2 좌표까지 직선을 그립니다.

이때 선 색상 또는 밝기는 color 인자를 통해 지정할 수 있습니다. 예를 들어, 3채널 컬러 영상에 빨간색 선을 그리려면 color 인자에 Scalar(0, 0, 255)를 전달하고, grayscale 영상에 검은색 선을 그리려면 0 또는 Scalar(0)을 전달합니다.

thinkness 인자는 선 두께를 의미하고, lineType 인자는 직선을 그리는 방식을 지정합니다. lineType 인자에는 lineTypes 열거형 상수 중 하나를 지정할 수 있으며, LineTypes 열거형 상수와 의미는 아래 표에 정리되어 있습니다.

LineTypes 열거형 상수 설명
FILLED -1 내부를 채움 (직선 그리기 함수에서는 사용 불가)
LINE_4 4 4방향 연결
LINE_8 8 8방향 연결
LINE_AA 16 anti-aliasing

 

line() 함수의 마지막 인자인 shift는 0보다 같거나 큰 정수를 지정할 수 있으며, 지정한 크기만큼 직선 좌표 값을 오른족 비트 시프트 연산(>>)을 수행한 후 직선 그리기 작업을 수행합니다. shift 인자 값이 0이면 지정한 좌표를 그대로 사용하여 그리기를 수행합니다. line() 함수의 인자 중에서 thinkness, lineType, shift는 기본값이 지정되어 있어 생략할 수 있습니다.

 

 

만약 화살표 형태의 직선을 그려야 하는 경우에는 arrowedLine() 함수를 이용하면 편리합니다.

사용되는 파라미터 정보는 다음과 같습니다.

arrowedLine() 함수는 img 영상 위에 pt1 좌표부터 pt2 좌표까지 직선을 그리고, 끝점인 pt2에 화살표 모양의 직선 두 개를 추가로 그립니다. 이때 화살표 모양의 직선 길이는 arrowedLIne() 함수의 마지막 인자 tipLength를 이용하여 조절할 수 있습니다. tipLength 인자는 기본값으로 0.1이 지정되어 있는데, 이는 직선 전체 길이의 0.1에 해당하는 길이로 화살표를 그리라는 의미입니다.

 

 

OpenCV 함수 중 drawMarker() 함수는 직선 그리기 함수를 이용하여 다양한 모양의 마커를 그립니다.

전달되는 파라미터 정보는 다음과 같습니다.

drawMarker() 함수는 img 영상의 position 좌표에 color 색상을 이용하여 마커를 그립니다. 마터 종류는 markerType 인자로 지정할 수 있으며, 기본값으로 십자가 모양을 의미하는 MARKER_CROSS가 지정되어 있습니다. 만약 다른 종류의 마커를 그리고 싶다면 markerType 인자에 MarkerTypes 열거형 상수 중 하나를 지정할 수 있습니다.

 

다음 코드는 위에서 언급한 line(), arrowedLine(), drawMarker() 함수를 이용하여 직선을 그리는 예제 코드입니다.

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

int main(int argc, char* argv[])
{
    cv::Mat img(400, 400, CV_8UC3, cv::Scalar(255, 255, 255));

    cv::line(img, cv::Point(50, 50), cv::Point(200, 50), cv::Scalar(0, 0, 255));
    cv::line(img, cv::Point(50, 100), cv::Point(200, 100), cv::Scalar(255, 0, 255), 3);
    cv::line(img, cv::Point(50, 150), cv::Point(200, 150), cv::Scalar(255, 0, 0), 10);

    cv::line(img, cv::Point(250, 50), cv::Point(350, 100), cv::Scalar(0, 0, 255), 1, cv::LINE_4);
    cv::line(img, cv::Point(250, 70), cv::Point(350, 120), cv::Scalar(255, 0, 255), 1, cv::LINE_8);
    cv::line(img, cv::Point(250, 90), cv::Point(350, 140), cv::Scalar(255, 0, 0), 1, cv::LINE_AA);

    cv::arrowedLine(img, cv::Point(50, 200), cv::Point(150, 200), cv::Scalar(0, 0, 255), 1);
    cv::arrowedLine(img, cv::Point(50, 250), cv::Point(350, 250), cv::Scalar(255, 0, 255), 1);
    cv::arrowedLine(img, cv::Point(50, 300), cv::Point(350, 300), cv::Scalar(255, 0, 0), 1, cv::LINE_8, 0, 0.05);

    cv::drawMarker(img, cv::Point(50, 350), cv::Scalar(0, 0, 255), cv::MARKER_CROSS);
    cv::drawMarker(img, cv::Point(100, 350), cv::Scalar(0, 0, 255), cv::MARKER_TILTED_CROSS);
    cv::drawMarker(img, cv::Point(150, 350), cv::Scalar(0, 0, 255), cv::MARKER_STAR);
    cv::drawMarker(img, cv::Point(200, 350), cv::Scalar(0, 0, 255), cv::MARKER_DIAMOND);
    cv::drawMarker(img, cv::Point(250, 350), cv::Scalar(0, 0, 255), cv::MARKER_SQUARE);
    cv::drawMarker(img, cv::Point(300, 350), cv::Scalar(0, 0, 255), cv::MARKER_TRIANGLE_UP);
    cv::drawMarker(img, cv::Point(350, 350), cv::Scalar(0, 0, 255), cv::MARKER_TRIANGLE_DOWN);

    cv::imshow("img", img);
    cv::waitKey(0);

    cv::destroyAllWindows();
}

위 코드의 실행 결과는 다음과 같습니다.

 


도형 그리기

이번에는 영상에 사각형, 원, 타원과 같은 도형을 그리는 함수에 대해 살펴보겠습니다.

 

먼저 영상에 사각형을 그리는 함수는 rectangle() 입니다.

rectangle() 함수는 위와 같이 두 가지 형식이 있습니다. 위의 파라미터 정보는 사각형의 두 정점(반대 위치의)을 받는 버전의 함수 인자입니다. 다른 형식으로는 Rect 정보를 입력받아 그리는 버전입니다.

 

함수 인자 중 thinkness는 도형 외곽선의 두께를 지정할 때 사용합니다. 만약 thinkness가 -1과 같은 음수 또는 FILLED 열거형 상수를 지정하면 내부를 채운 사각형을 그립니다. rectangle() 함수 인자 중에서 thinkness, lineType, shift 인자는 기본값을 가지고 있으므로 생략할 수 있습니다.

 

 

OpenCV에서 원을 그리는 함수는 circle() 입니다. 원을 그리기 위해서는 원의 중심점 좌표와 반지름을 지정해야 합니다.

 

 

OpenCV에서 타원을 그리고 싶을 때에는 ellipse() 함수를 사용할 수 있습니다.

타원을 그리는 방식은 원보다는 조금 더 복잡합니다. ellipse() 함수를 통해 다양한 형태의 타원 또는 타원의 일부인 호를 그릴 수 있습니다. 타원의 크기는 axes 인자를 통해 지정합니다. axes 인자는 Size 타입을 사용하며, x축 방향 타원의 반지름과 y축 방향 반지름을 지정합니다. angle 인자에 0이 아닌 값을 전달하면 회전된 타원을 그릴 수 있습니다. startAngle과 endAngle 인자를 적절하게 이용하면 호를 그리는 용도로 사용할 수 있습니다. thickness 인자는 타원 외곽선 두께를 나타내며 이 값을 -1 또는 FILLED로 설정하면 내부를 채운 타원 혹은 호를 그립니다.

 

 

임의의 다각형을 그리기 위해서는 polylines() 함수를 사용할 수 있습니다. polylines() 함수에는 다각형의 꼭지점 좌표를 전달하며, 꼭지점 좌표는 보통 vector<Point> 타입에 저장하여 전달합니다.

(다른 버전의 polylines() 함수도 있습니다)

여기서 isClosed는 다각형이 닫혀 있는지를 나타내는 플래그로써, 이 값이 true라면 다각형의 마지막 꼭지점과 첫 번째 꼭지점을 잇는 직선을 추가로 그립니다.

 

 

아래 코드는 위에서 언급한 함수들을 사용하여 그림을 그리는 예제 코드입니다.

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

int main(int argc, char* argv[])
{
    cv::Mat img(400, 400, CV_8UC3, cv::Scalar(255, 255, 255));

    cv::rectangle(img, cv::Rect(50, 50, 100, 50), cv::Scalar(0, 0, 255), 2);
    cv::rectangle(img, cv::Rect(50, 150, 100, 50), cv::Scalar(0, 0, 128), -1);

    cv::circle(img, cv::Point(300, 120), 30, cv::Scalar(255, 255, 0), -1, cv::LINE_AA);
    cv::circle(img, cv::Point(300, 120), 60, cv::Scalar(255, 0, 0), 3, cv::LINE_AA);

    std::vector<cv::Point> pts;
    pts.push_back(cv::Point(250, 250));
    pts.push_back(cv::Point(300, 250));
    pts.push_back(cv::Point(300, 300));
    pts.push_back(cv::Point(350, 250));
    pts.push_back(cv::Point(350, 350));
    pts.push_back(cv::Point(250, 350));

    cv::polylines(img, pts, true, cv::Scalar(255, 0, 255), 2);

    cv::imshow("img", img);
    cv::waitKey(0);

    cv::destroyAllWindows();
}

 


문자열 출력

OpenCV는 영상 위에 정해진 폰트로 문자열을 출력하는 putText() 함수를 제공합니다.

파라미터 정보는 다음과 같습니다.

putText() 함수는 img 영상의 org 위치에 text로 지정된 문자열을 출력합니다. 이때 사용할 폰트 종류는 fontFace 인자로 지정할 수 있고, faceScale 인자를 이용하여 폰트 크기를 조절할 수 있습니다. fontFace 인자에는 HersheyFonts 열거형 상수 값을 지정할 수 있으며, 그 종류는 다음과 같습니다.

위의 표에서 마지막 FONT_ITALIC 상수는 논리합 연산자(|)를 이용하여 다른 HersheyFonts 상수와 함께 사용하며, 이 경우 해당 폰트가 기울어진 italic체로 출력됩니다. putText() 함수는 영문자와 숫자를 출력할 수 있으며 한글은 출력할 수 없습니다.

 

다음 코드는 putText()를 이용하여 다양한 문자 출력을 보여주는 예제 코드입니다.

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

int main(int argc, char* argv[])
{
    cv::Mat img(500, 800, CV_8UC3, cv::Scalar(255, 255, 255));

    cv::putText(img, "FONT_HERSHEY_SIMPLEX", cv::Point(20, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255));
    cv::putText(img, "FONT_HERSHEY_PLAIN", cv::Point(20, 100), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255));
    cv::putText(img, "FONT_HERSHEY_DUPLEX", cv::Point(20, 150), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 0, 255));
    cv::putText(img, "FONT_HERSHEY_COMPLEX", cv::Point(20, 200), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 0, 0));
    cv::putText(img, "FONT_HERSHEY_TRIPLEX", cv::Point(20, 250), cv::FONT_HERSHEY_TRIPLEX, 1, cv::Scalar(255, 0, 0));
    cv::putText(img, "FONT_HERSHEY_COMPLEX_SMALL", cv::Point(20, 300), cv::FONT_HERSHEY_COMPLEX_SMALL, 1, cv::Scalar(255, 0, 0));
    cv::putText(img, "FONT_HERSHEY_SCRIPT_SIMPLEX", cv::Point(20, 350), cv::FONT_HERSHEY_SCRIPT_SIMPLEX, 1, cv::Scalar(255, 0, 255));
    cv::putText(img, "FONT_HERSHEY_SCRIPT_COMPLEX", cv::Point(20, 400), cv::FONT_HERSHEY_SCRIPT_COMPLEX, 1, cv::Scalar(255, 0, 255));
    cv::putText(img, "FONT_HERSHEY_COMPLEX | FONT_ITALIC", cv::Point(20, 450), cv::FONT_HERSHEY_COMPLEX | cv::FONT_ITALIC, 1, cv::Scalar(255, 0, 0));

    cv::imshow("img", img);
    cv::waitKey(0);

    cv::destroyAllWindows();
}

 

 

OpenCV는 문자열 출력을 위해 필요한 사각형 영역 크기를 가늠할 수 있는 getTextSize() 함수도 제공합니다. 이 함수를 잘 이용하면 문자열이 한쪽으로 치우치지 않고 적당한 위치에 출력되도록 설정할 수 있습니다.

getTextSize() 함수는 지정한 문자열, 폰트 종류, 폰트 크기 등을 이용하여 문자열을 출력할 때 차지할 사각형 영역 크기 정보를 반환합니다. putText() 함수를 이용하여 특정 위치 좌표에 문자열을 출력하는 경우, 보통 문자열의 길이와 크기에 따라 문자열이 차지하는 영역 크기가 달라지기 때문에 문자열이 한쪽으로 치우쳐서 나타날 수 있습니다. 그러나 getTextSize() 함수가 반환하는 문자열 영역 크기 정보를 이용하면 문자열 출력 위치를 적절하게 조절할 수 있습니다.

 

아래 코드는 getTextSize() 함수를 이용하여 영상 중앙에 문자열을 출력하는 예제 코드입니다. 정중앙에 "Hello, OpenCV"라는 문자열을 출력합니다.

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

int main(int argc, char* argv[])
{
    cv::Mat img(200, 640, CV_8UC3, cv::Scalar(255, 255, 255));

    const cv::String text = "Hello, OpenCV";
    int fontFace = cv::FONT_HERSHEY_TRIPLEX;
    double fontScale = 2.0;
    int thinkness = 1;

    cv::Size sizeText = cv::getTextSize(text, fontFace, fontScale, thinkness, 0);
    cv::Size sizeImg = img.size();

    cv::Point org((sizeImg.width - sizeText.width) / 2, (sizeImg.height + sizeText.height) / 2);
    cv::putText(img, text, org, fontFace, fontScale, cv::Scalar(255, 0, 0), thinkness);
    cv::rectangle(img, org, org + cv::Point(sizeText.width, -sizeText.height), cv::Scalar(255, 0, 0), 1);

    cv::imshow("img", img);
    cv::waitKey(0);

    cv::destroyAllWindows();
}

 

위 코드에서 img.size()를 통해 img 영상의 크기를 받아와서 sizeImg에 저장합니다. 따라서 영상의 가로 크기는 sizeImg.width, 세로 크기는 sizeImg.height로 참조할 수 있습니다. 출력한 문자열이 차지할 영역의 크기는 getTextSize()를 통해 sizeText 변수에 저장했습니다. 영상의 크기와 실제 문자열이 출력된 사각형의 크기를 알기 때문에 이를 이용하여 문자열이 출력될 사각형 영역의 좌측 하단 좌표를 다음과 같이 계산할 수 있습니다.

cv::Point org((sizeImg.width - sizeText.width) / 2, (sizeImg.height + sizeText.height) / 2);

아래 그림은 함수 동작을 쉽게 이해하기 위해 함수 내부에서 사용되는 변수의 의미를 함께 보여주고 있습니다.

 

OpenCV의 그리기 함수들은 영상의 픽셀 값을 변경시켜 그림을 그리므로, 일반 영상 위에 그리게 되면 원본 영상의 픽셀 값은 복구할 수 없게 됩니다. 만약 영상에 그리기를 수행한 후에 다시 원본 영상을 사용할 필요가 있다면 미리 원본 영상을 복사해 두고 사용해야 합니다.

 

댓글