해당 포스팅은 Mastering OpenCV 4 with Python 원서를 바탕으로 작성했습니다. 원서를 옮기는 과정에서 부자연스러운 부분이 있을 수 있습니다. 잘못 작성되거나 어색한 부분에 대해서 알려주시면 감사하겠습니다! 코드 정보는 여기를 클릭하시면 확인하실 수 있습니다.
지난 포스팅에서 OpenCV를 활용해서 다양한 도형을 그리는 방법들과 도형을 그렸을 때, 어떤 장점이 있는 지에 대한 내용을 다뤘습니다. 이번 글에서는 지난 글에 이어서 기본 도형을 소개하고, 이미지에 text를 넣는 방법을 함께 소개합니다.
- OpenCV를 활용한 그리기 이론적 소개
- 기본 도형 그리기 - 선, 사각형, 원
- 기본 도형 2 - 클립, 화살표, 타원, 폴리라인
- 텍스트 입력하기
- 마우스 Event에 따른 Dynamic Drawing
- 그리기 고급편
1. 기본 도형 그리기 - 클립, 화살표, 타원, 폴리라인
이번 장에서 소개하는 도형들은 이전 포스팅에서 그렸던 도형들에 비해 그리기가 쉽지 않습니다. 그렇지만 조금만 이해한다면 간단하게 그릴 수 있습니다. 이전에 포스팅에서 했던 것과 동일하게 도형을 그릴 캔버스가 필요합니다.
import numpy as np
# We set the background to black using np.zeros()
image = np.zeros((300, 300, 3), dtype="uint8")
# We set the background to light_gray using colors dictionary
image[:] = colors['light_gray']
캔버스가 만들어졌다면, 클립라인을 그려봅시다. 클립라인은 직사각형 내에 직선이 존재할 때, 해당 직선과 직사각형이 만나는 좌표를 알아내기 위한 함수입니다.
# clipLine
retval, pt1, pt2 = cv2.clipLine(imgRect, pt1, pt2)
retval은 boolean 타입으로 교차하는 점이 있다면 True, 없다면 False 값을 포함합니다. 등호 앞의 pt1, pt2는 사각형과 직선이 만나는 점들을 반환하는 값입니다. cv2.clipLine 내부의 imgRect은 사각형의 좌상단 점과 우하단 점을 tuple로 넣어주면 되고, pt1, pt2는 사각형을 가로지르는 직선의 시작점과 끝점입니다.
#example
cv2.line(image, (0, 0), (300, 300), colors['green'], 3)
cv2.rectangle(image, (0, 0), (100, 100), colors['blue'], 3)
ret, p1, p2 = cv2.clipLine((0, 0, 100, 100), (0, 0), (300, 300))
if ret:
cv2.line(image, p1, p2, colors['yellow'], 3)
위의 예제코드를 실행하면, 초록색 선과 파란색 사각형이 (0,0)과 (100, 100)에서 만나게 됩니다. 그래서 해당 지점인 p1, p2로 노란색 선을 그리면 아래와 같은 결과를 얻을 수 있습니다.
다음으로 그려볼 것은 화살표입니다.
# Syntax
cv2.arrowedLine(image, pt1, pt2, color, thickness=1, lineType=8, shift=0, tipLength=0.1)
image는 우리가 그릴 이미지이며, pt1, pt2는 화살표를 그릴 때 주축이 되는 직선의 좌표입니다. thickness는 화살표의 굵기를 표현하는 부분이고, lineType과 shift는 이전의 포스팅에서 설명했으니, 참고하시면 좋을 것 같습니다. tipLength는 pt1과 pt2를 기준으로 화살표의 길이를 설정하게 됩니다. 예를 들어, 0.1로 설정했다면, pt1과 pt2 사이의 길이의 10분의 1의 크기로 화살표 부분을 그립니다.
cv2.arrowedLine(image, (50, 50), (200, 50), colors['red'], 3, 8, 0, 0.1)
cv2.arrowedLine(image, (50, 120), (200, 120), colors['green'], 3, cv2.LINE_AA, 0, 0.3)
cv2.arrowedLine(image, (50, 200), (200, 200), colors['blue'], 3, 8, 0, 0.3)
위의 예제를 실행하면 아래와 같은 결과를 얻을 수 있습니다. 여기서 lineType=8은 cv2.LINE_8과 동일합니다. cv2.LINE_AA는 16이라는 값으로 대신 사용할 수 있습니다. 빨간색 화살표에 비해, 초록색과 파란색의 화살표 크기가 큰 것은 0.3으로 설정했기 때문입니다.
이번에는 타원을 한번 그려보려고 합니다. 타원은 아래와 같은 문법으로 사용할 수 있습니다.
# Syntax
cv2.ellipse(image, center, axes, angle, startAngle, endAngle, color, thickness=1, lineType=8, shift=0)
center는 타원이 그려질 위치의 장축과 단축이 만나는 지점 좌표입니다. axes는 각각 축의 길이의 절반을 순서쌍으로 입력해줍니다. angle은 해당 값에 의하여 타원을 회전시킬 수 있습니다. 해당 각도만큼 시계방향으로 돌립니다. startAngle과 endAngle은 타원을 해당 각도 영역만큼만 그립니다. 만약, 중간에 그만그리고 싶을 때에는 해당 그리고자 하는 각도만 입력해주면 됩니다.
# example
cv2.ellipse(image, (80, 80), (60, 40), 0, 0, 360, colors['red'], -1)
cv2.ellipse(image, (80, 200), (80, 40), 0, 0, 360, colors['green'], 3)
cv2.ellipse(image, (80, 200), (10, 40), 0, 0, 360, colors['blue'], 3)
cv2.ellipse(image, (200, 200), (10, 40), 0, 0, 180, colors['yellow'], 3)
cv2.ellipse(image, (200, 100), (10, 40), 0, 0, 270, colors['cyan'], 3)
cv2.ellipse(image, (250, 250), (30, 30), 0, 0, 360, colors['magenta'], 3)
cv2.ellipse(image, (250, 100), (20, 40), 45, 0, 360, colors['gray'], 3)
빨간색 타원을 보면 thickness로 -1을 입력했더니 내부까지 색칠된 타원을 얻을 수 있습니다. 또한 하늘색과 노란색 타원을 보면, startAngle과 endAngle이 x축을 기준으로 시계방향으로 돌아가는 것을 알 수 있습니다. 회색 타원은 원래 위로 길쭉한 타원이었으나 시계방향으로 45도 이동한 타원을 얻었습니다.
마지막으로 다각형을 그려보려고 합니다. 다각형을 그릴 때에는 cv2.polylines() 명령어를 사용합니다.
# Syntax
cv2.polylines(image, pts, isClosed, color, thickness=1, lineType=8, shift=0)
pts는 폴리곤 곡선을 정의하는 배열이 들어가야 합니다. 해당 배열은 항상 (꼭지점의 수, 1, 2)의 형태로 입력되어야 합니다. 그래서 pts를 입력하기 전에 reshape을 통해 pts 배열을 수정합니다. 일반적으로 아래의 형태처럼 np.array로 정의한 뒤 reshape를 진행합니다.
# np.array로 꼭지점 입력
pts = np.array([[250, 5], [220, 80], [280, 80]], np.int32)
# pts reshape
pts = pts.reshape((-1, 1, 2))
isClosed라는 파라미터는 다각형이 선으로 완성되는 지에 대한 여부를 입력으로 받습니다. True이라면, 마지막 좌표와 첫번째 좌표를 이어서 닫혀진 다각형을 만들고, False라면 끊겨진 선의 형태를 갖습니다.
pts = np.array([[250, 5], [220, 80], [280, 80]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(image, [pts], True, colors['green'], 3)
pts = np.array([[250, 105], [220, 180], [280, 180]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(image, [pts], False, colors['green'], 3)
pts = np.array([[20, 90], [60, 60], [100, 90], [80, 130], [40, 130]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(image, [pts], True, colors['blue'], 3)
pts = np.array([[20, 180], [60, 150], [100, 180], [80, 220], [40, 220]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(image, [pts], False, colors['blue'], 3)
pts = np.array([[150, 100], [200, 100], [200, 150], [150, 150]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(image, [pts], True, colors['yellow'], 3)
pts = np.array([[150, 200], [200, 200], [200, 250], [150, 250]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(image, [pts], False, colors['yellow'], 3)
위의 예제코드를 실행하면 아래와 같은 결과를 얻을 수 있습니다. 보시는 것처럼 False로 되어진 경우에는 완성되지 않은 다각형이 만들어지고, True인 경우에는 마지막 꼭지점과 처음 꼭지점을 이어서 다각형을 완성합니다.
2. 텍스트 입력하기
OpenCV 라이브러리는 이전에서 보여준 도형 외에도 텍스트를 이미지에 추가할 수 있습니다. 이번 장에서는 cv2.putText()를 활용하여 text를 입력하는 방법을 소개합니다.
# Syntax
cv2.putText(image, text, org, fontFace, fontScale, color, thickness=1, lineType=8, bottomLeftOrigin=False)
org는 text가 작성될 위치의 좌하단 좌표입니다. 다만, bottomLeftOrigin 값이 False라면, 좌상단 좌표가 입력되어야 합니다. fontFace와 fontScale은 입력될 Text의 font와 크기를 입력으로 받습니다. bottomLeftOrigin은 True일 때에좌측 하단을 기준으로 잡고, False 일때에는 좌측 상단을 기준으로 잡습니다.
text = 'Mastering OpenCV4 with Python'
cv2.putText(image, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_4)
cv2.putText(image, text, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_8)
cv2.putText(image, text, (10, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_AA)
위의 코드에서 이전과 살짝 다른 것은 canvas의 크기를 (300, 300) 에서 (120, 500)으로 수정하였고, 배경은 light_gray에서 white로 수정했습니다. 3개의 예시는 모두 동일하고 lineType만 다르게 적용하였습니다. 결과는 아래와 같습니다.
fontFace에 사용할 수 있는 목록은 아래와 같습니다. cv2로 시작하는 값을 넣어도 되지만 각 값에 해당하는 숫자로 대체하여 사용할 수 있습니다.
- cv2.FONT_HERSHEY_SIMPLEX = 0
- cv2.FONT_HERSHEY_PLAIN = 1
- cv2.FONT_HERSHEY_DUPLEX = 2
- cv2.FONT_HERSHEY_COMPLEX = 3
- cv2.FONT_HERSHEY_TRIPLEX = 4
- cv2.FONT_HERSHEY_COMPLEX_SMALL = 5
- cv2.FONT_HERSHEY_SCRIPT_SIMPLEX = 6
- cv2.FONT_HERSHEY_SCRIPT_COMPLEX = 7
# 입력할 내용 dictionary로 생성
fonts = {0: "FONT HERSHEY SIMPLEX", 1: "FONT HERSHEY PLAIN", 2: "FONT HERSHEY DUPLEX", 3: "FONT HERSHEY COMPLEX", 4: "FONT HERSHEY TRIPLEX",
5: "FONT HERSHEY COMPLEX SMALL ", 6: "FONT HERSHEY SCRIPT SIMPLEX", 7: "FONT HERSHEY SCRIPT COMPLEX"}
# 사용할 색상 dictionary로 생성
index_colors = {0: 'blue', 1: 'green', 2: 'red', 3: 'yellow', 4: 'magenta', 5: 'cyan', 6: 'black', 7: 'dark_gray'}
# canvas 만들기
image = np.zeros((650, 650, 3), dtype="uint8")
# 배경을 하얀색으로 변경
image[:] = colors['white']
# 다양한 폰트로 작성하기
position = (10, 30)
for i in range(0, 8):
cv2.putText(image, fonts[i], position, i, 1.1, colors[index_colors[i]], 2, cv2.LINE_4)
position = (position[0], position[1] + 40)
cv2.putText(image, fonts[i].lower(), position, i, 1.1, colors[index_colors[i]], 2, cv2.LINE_4)
position = (position[0], position[1] + 40)
# 입력(이전 포스팅에서 소개)
show_with_matplotlib(image, 'cv2.putText() using all OpenCV fonts')
position은 각 텍스트끼리 겹치지 않도록 기준점을 잡아주는 역할이며, 아래의 결과처럼 다양한 폰트와 크기로 텍스트를 이미지에 추가할 수 있습니다.
이외에도 text와 관련있는 다양한 함수들이 존재합니다.
- cv2.getFontScaleFromHeight() : fontFace와 pixel높이와 굵기가 주어졌을 때, 알맞은 fontScale을 반환하는 함수
- cv2.getTextSize() : text와 fontFace, fontScale, 굵기가 주어졌을 때, 알맞은 텍스트의 사이즈를 반환하는 함수
# Syntax
retval = cv2.getFontScaleFromHeight(fontFace, pixelHeight, thickness=1)
# Syntax
retval, baseLine = cv2.getTextSize(text, fontFace, fontScale, thickness)
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 2.5
thickness = 5
text = 'abcdefghijklmnopqrstuvwxyz'
circle_radius = 10
# We get the size of the text:
ret, baseline = cv2.getTextSize(text, font, font_scale, thickness)
# We get the text width and text height from ret:
text_width, text_height = ret
# We center the text in the image:
text_x = int(round((image.shape[1] - text_width) / 2))
text_y = int(round((image.shape[0] + text_height) / 2))
# Draw this point for reference:
cv2.circle(image, (text_x, text_y), circle_radius, colors['green'], -1)
# Draw the rectangle (bounding box of the text):
cv2.rectangle(image, (text_x, text_y + baseline), (text_x + text_width - thickness, text_y - text_height), colors['blue'], thickness)
# Draw the circles defining the rectangle:
cv2.circle(image, (text_x, text_y + baseline), circle_radius, colors['red'], -1)
cv2.circle(image, (text_x + text_width - thickness, text_y - text_height), circle_radius, colors['cyan'], -1)
# Draw the baseline line:
cv2.line(image, (text_x, text_y + int(round(thickness / 2))), (text_x + text_width - thickness, text_y + int(round(thickness / 2))), colors['yellow'],
thickness)
# Write the text centered in the image:
cv2.putText(image, text, (text_x, text_y), font, font_scale, colors['magenta'], thickness)
# Show image:
show_with_matplotlib(image, 'cv2.getTextSize() + cv2.putText()')
getTextSize를 통해서 (text_width, text_height), baseline을 얻을 수 있습니다. 위의 예제에서는 ret 값을 이용해서 좌하단 지점인 text_x, text_y를 구하고 해당 지점을 초록색 원으로 표현했습니다. 그 후, 좌하단 점을 기준으로 baseline을 활용하여 파란색 사각형을 그립니다. 그려진 결과는 아래와 같습니다.
3. 2줄 요약
- 다양한 형태의 도형(clipline, 화살표, 타원, 다각형)들을 손쉽게 이미지에 추가할 수 있습니다.
- Text를 다양한 font와 scale에 맞게 이미지에 추가할 수 있습니다.