cv2.imread(filename, flages) : 이미지를 읽어오는 함수flags : 색상 모드 지정 (ex) cv2.IMREAD_COLOR - 컬러 이미지로 읽기, cv2.IMREAD_GRAYSCALE - 그레이스케일로 읽기, cv2.IMREAD_UNCHANGED - 알파 채널까지 그대로 읽cv2.imshow(winname, mat) : 이미지를 화면에 보여주는 함수winname (str) : 이미지를 표시할 윈도우 창의 제목mat (numpy.ndarray) : 표시할 이미지 데이터 (보통 imread로 불러온 행렬)# 이미지를 컬러로 읽기 (file_name, flags)
img_color = cv2.imread(file_name, cv2.IMREAD_COLOR)
# 읽어온 이미지를 Colab 노트북 창에서 띄우기
cv2_imshow(img_color)
cv2.imwrite(filename, img) : 이미지를 파일로 저장하는 함수filename : 저장될 파일 경로img : 저장할 이미지(Mat) 객체cv2.imwrite("saved_image.jpg", img_color)
print("이미지가 저장되었습니다 : saved_image.jpg")
cv2.cvtColor(src, code) : 이미지의 색상 공간을 변환하는 함수src : 변환할 원본 이미지code : 변환 방법 코드 (ex) cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSVimg_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
cv2.resize(src, dsize) : 이미지 크기를 변경하는 함수src : 리사이즈할 원본 이미지dsize : (가로, 세로) 순서로 새로운 이미지 크기fx, fy : 비율을 지정해 리사이즈 가능cv2.INTER_LINEAR가 적용됨.# 이미지 크기 변경 (50% 축소)
h, w = img_color.shape[:2]
img_resized = cv2.resize(img_color, (w // 2, h // 2))
# 크기 변경된 이미지 출력
cv2_imshow(img_resized)
# 저장
cv2.imwrite("resized_image.jpg", img_resized)
OpenCV는 기본적으로 이미지를 BGR(Blue, Green, Red) 순서로 읽으나, 필요에 따라 다른 색상 공간으로 변환할 수 있다.
HSI는 Hue-channel, Saturation-channel, Intensity-channel로 구성된다. 여기서는 색상, 채도, 명도라는 3가지 특성들이 색을 설명하는 데 사용되며, 어떤 컬러 모델을 만들어 낼 때 얼마나 색을 사용했는지 알 필요 없이 단순히 채도와 명도를 통해 색을 조절한다.
Intensity (Brightness) : 어떤 색상의 밝기 값을 의미하며, 색상 정보는 포함되어 있지 않음.
Hue : 색조 (우세한 파장이 무엇인지에 관한 것)S의 파장이 크다면 clear color
Saturation : 색상의 상대적인 순도 혹은 백색이 섞인 양. 순도가 높을수록(색이 선명할 수록) 이 값이 커진다.S가 크면 선명한 색상, I가 크면 밝기가 밝은 색상이다.
HSI 모델에서 Hue는 원의 각도, Saturation은 원의 bound, Intensity는 높이를 통해 알 수 있다.

이러한 HSV를 사용하는 이유는 이미지에서 색상 정보를 검출하고자 할 때, Hue를 사용하면 되므로 RGB보다 분류가 더 쉽기 때문이다. 아래의 예시를 보면 Hue를 통해 특정 범위의 색상만 마스킹해 추출한다.
이렇게 색상을 추출할 땐 다음과 같은 주의 사항이 있다.
# 특정 색상 범위를 HSV로 정의해둔 예시
# 각 색은 HSV에서의 범위를 lower(최소값), upper(최대값)로 저장.
color_ranges = {
'red': {'lower': (0, 100, 100), 'upper': (10, 255, 255)}, # 빨간색 범위
'green': {'lower': (40, 100, 100), 'upper': (70, 255, 255)}, # 초록색 범위
'blue': {'lower': (100, 100, 100), 'upper': (130, 255, 255)} # 파란색 범위
}
# 사용자로부터 색상 입력 받기
user_color = 'red'
# HSV 이미지 준비
img_hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV)
# 색상 범위 가져오기
lower_val = color_ranges[user_color]['lower']
upper_val = color_ranges[user_color]['upper']
# inRange 함수를 사용해 특정 범위 색상만 추출 (마스크 생성)
mask = cv2.inRange(img_hsv, lower_val, upper_val)
# 마스크를 이용해 원본 이미지에서 특정 색상 영역만 추출
# bitwise_and 연산으로 마스크가 1인 영역만 남김
img_masked = cv2.bitwise_and(img_color, img_color, mask=mask)
# 결과 출력
print(f"선택된 색상 : {user_color}")
cv2_imshow(mask)
cv2_imshow(img_masked)
image.shape : (Height, Width, Channels)image.size : 전체 픽셀 수 (Height × Width × Channels)image[y, x] : (y, x) 위치의 픽셀(BGR) 정보를 배열 형태로 얻음.image[y, x][0] = Blue, image[y, x][1] = Green, image[y, x][2] = Red# Colab에 이미지 업로드
uploaded = files.upload()
# 업로드된 첫 번째 이미지 파일 이름 가져오기
filename = list(uploaded.keys())[0]
# 이미지를 BGR 컬러로 읽기
image = cv2.imread(filename, cv2.IMREAD_COLOR)
print("이미지 로드 완료! 파일명 : ", filename)
# 이미지 기본 정보 확인
print("이미지 크기 (Height, Width, Channels) : ", image.shape)
print("이미지 전체 픽셀 수 : ", image.size)
# 특정 픽셀의 BGR 값 확인
y, x = 100, 100 # 예 : (100, 100) 위치
if y < image.shape[0] and x < image.shape[1]:
px = image[y, x]
print(f"({y}, {x}) 위치 픽셀의 BGR 값 :", px)
print(f"({y}, {x}) 위치 픽셀의 B 값 :", px[0])
print(f"({y}, {x}) 위치 픽셀의 G 값 :", px[1])
print(f"({y}, {x}) 위치 픽셀의 R 값 :", px[2])
else:
print(f"이미지 크기가 100*100보다 작아서 ({y}, {x}) 픽셀이 존재하지 않습니다.")

OpenCV 이미지 객체는 numpy 배열과 호환되므로, 2차원(또는 3차원) 슬라이싱을 통해 손쉽게 특정 영역을 변경할 수 있다. 이때, 특정 영역을 변경하는 방법에는 반복문을 사용해 픽셀을 하나씩 변경하는 방법과 슬라이싱을 통해 한 번에 처리하는 방법이 있다.
for i in range(...): for j in range(...) ... → 각 픽셀에 직접 접근해 값을 대입하는 방식으로, 반복문을 통해 하나의 픽셀씩 변경하므로 이미지 크기가 커질수록 상대적으로 속도가 느려진다는 단점이 있다.image[y1:y2, x1:x2] = [B, G, R]# 이미지 복사본 생성
image_copy = image.copy()
# (1) 반복문을 이용한 특정 범위 변경
start_time = time.time()
rows = min(100, image_copy.shape[0]) # 이미지가 100보다 작을 수 있으므로
cols = min(100, image_copy.shape[1])
for i in range(rows):
for j in range(cols):
image_copy[i, j] = [255, 255, 255] # 흰색으로 변경
end_time = time.time()
print("-- 반복문 방식 수행 시간 : %.6f seconds --" % (end_time - start_time))
# (2) 슬라이싱을 이용한 특정 범위 변경
image_copy2 = image.copy()
start_time = time.time()
image_copy2[0:rows, 0:cols] = [0, 0, 0] # 검정색으로 변경
end_time = time.time()
print("-- 슬라이싱 방식 수행 시간 : %.6f seconds --" % (end_time - start_time))
# 결과 이미지 보기
print("\n[반복문 방식] 상단 100*100 영역을 흰색으로 변경")
cv2_imshow(image_copy)
print("\n[슬라이싱 방식] 상단 100*100 영역을 검정색으로 변경")
cv2_imshow(image_copy2)
roi = image[y1:y2, x1:x2] : ROI 영역 추출image_copy[새로운 위치] = roi : 추출된 영역을 다른 위치에 복사# ROI 추출 예시 : (y : 20~150, x : 70~200)
image_copy3 = image.copy()
# 안전하게 min() 함수를 사용하여 이미지 범위 내에서만 추출
roi = image_copy3[20:min(150, image_copy3.shape[0]), 70:min(200, image_copy3.shape[1])]
# 잘라낸 ROI 확인
print("[ROI] 20:150, 70:200 영역")
cv2_imshow(roi)
# ROI를 다른 위치(0:130, 0:130)에 복사
h_roi, w_roi = roi.shape[:2]
end_y = min(h_roi, image_copy3.shape[0])
end_x = min(w_roi, image_copy3.shape[1])
image_copy3[0:end_y, 0:end_x] = roi
print("[Modified image] ROI를 좌측 상단(0:130, 0, 130)에 복사")
cv2_imshow(image_copy3)
OpenCV 이미지(3채널)는 BGR 순서로 채널을 저장하며, 특정 채널만 확인하거나 특정 채널만 제거할 수도 있다.
image[:, :, 0] : Blue 채널image[:, :, 1] : Green 채널image[:, :, 2] : Red 채널# (1) Blue 채널만 확인
blue_channel = image[:, :, 0]
print("[Blue Channel]")
cv2_imshow(blue_channel)
# (2) 특정 색상 채널 제거 (예 : Red 채널을 0으로)
image_no_red = image.copy()
image_no_red[:, :, 2] = 0 # Red 채널 제거
print("[Red 채널 제거]")
cv2_imshow(image_no_red)
이미지의 크기를 변경(확대/축소)할 때에는 cv2.resize() 함수를 사용함.
resized = cv2.resize(
src, # 원본 이미지
dsize, # (width, height) - 직접 지정하거나 None으로 둘 수 있음
fx=1.0, # 가로 방향 배율
fy=1.0, # 세로 방향 배율
interpolation=cv2.INTER_LINEAR # 보간법(기본값: INTER_LINEAR)
)
이미지를 확대/축소할 때 실제 존재하지 않는 픽셀 사이의 값을 채우거나 제거해야 하는데, 이 픽셀을 어떻게 계산해서 값을 구할지 결정하는 방법을 보간법(Interpolation)이라고 한다.
cv2.INTER_NEAREST : 최근접 이웃 보간법 (Nearest Neighbor)
cv2.INTER_LINEAR : 양선형 보간법 (Bilinear Interpolation)
cv2.INTER_CUBIC : 4*4 이웃 픽셀을 고려하는 삼차 보간법 (Bicubic Interpolation)
cv2.INTER_AREA : 영역 보간법
cv2.INTER_LANCZOS4 : Lanczos 보간법
가장 단순한 방법은 Nearest로, 이웃 픽셀 정보를 이용하기에 단순하지만 이미지의 연속성이 소실될 수 있다. 양선형 보간법인 LINEAR는 일반적으로 좋은 성능을 내기에 기본값으로 사용되고, 큐빅(삼차 보간법)은 4*4 이웃 픽셀을 고려하므로 확대 시 성능이 가장 좋다. AREA는 영역 보간법으로 주로 축소 시에 좋은 결과를 내고, Lanczos는 고품질에 유용하다. (확대 = CUBIC, 축소 = AREA)
cv2.warpAffine(src, M, dsize)tx : x축으로 이동하는 픽셀 수 (양수 → 오른쪽 이동, 음수 → 왼쪽 이동)ty : y축으로 이동하는 픽셀 수 (양수 → 아래쪽 이동, 음수 → 위쪽 이동)dsize : 결과 이미지의 크기. 일반적으로 원본과 같은 크기를 지정합니다.M = np.float32([
[1, 0, tx], # x 방향 이동
[0, 1, ty] # y 방향 이동
])
dst = cv2.warpAffine(
src=image,
M=M, # 변환 행렬
dsize=(width, height) # 결과 이미지 크기
)

cv2.getRotationMatrix2D(center, angle, scale)center : (cx, cy) 형태의 회전 중심점 좌표angle : 회전 각도 (+일 때 반시계 방향)scale : 회전하면서 이미지 크기를 확대/축소하는 비율 (1.0 → 원본)M = cv2.getRotationMatrix2D(
center, # 회전 중심 (tuple)
angle, # 회전 각도 (도 단위, +값 = 반시계방향)
scale # 크기 비율 (1.0이면 원본 크기 유지)
)
rotated = cv2.warpAffine(image, M, (width, height))
# 예시 코드
height, width = image.shape[:2] # 이미지의 높이, 너비
# 회전 중심 (이미지의 중앙)
center = (width / 2, height / 2)
# 90도 회전 + 0.5배 스케일 (반시계방향)
M = cv2.getRotationMatrix2D(
center=center,
angle=90,
scale=0.5
)
# 회전 적용
rotated = cv2.warpAffine(
image, # 원본 이미지
M, # 회전 행렬
(width, height) # 출력 이미지 크기
)
print("[이미지 회전 : 90도 스케일 0.5]")
cv2_imshow(rotated)
cv2.add() vs Numpy +uint8이므로 값의 범위는 주로 0~255이다.cv2.add() : 포화 연산(saturation arithmetic)을 사용하므로 255를 넘으면 더이상 커지지 않고 값이 255로 고정되는 특성이 있음. (ex) 250 + 10 = 255 → 영상 픽셀의 범위가 uint8 일 때, 0 ≤ pixel ≤ 255를 유지하도록 함.+ : uint8 배열끼리 + 를 하면 overflow가 발생할 수 있고, uint8에서 overflow가 나면 wrap-around가 발생할 수 있음.Alpha blending은 두 영상을 일정 비율로 섞는 방법이다.
alpha가 크면 첫 번째 영상이 더 많이 반영되고, alpha가 작으면 두 번째 영상이 더 많이 반영됨.blended = cv2.addWeighted(src1, alpha, src2, beta, gamma)alpha : 첫 번째 이미지의 가중치beta : 두 번째 이미지의 가중치gamma : 전체 결과에 추가되는 상수 값h, w = image_1_aligned.shape[:2]
mask_circle = np.zeros((h, w), dtype=np.uint8)
center = (w // 2, h // 2)
radius = min(h, w) // 4
cv2.circle(mask_circle, center, radius, 255, -1) # 마스크에 흰색 원 그리기
# 원 안쪽 = 255, 바깥 = 0
mask_circle_inv = cv2.bitwise_not(mask_circle) # 마스크 반전
# img1에서 원 안쪽만 추출 (전경)
fg_circle = cv2.bitwise_and(image_1_aligned, image_1_aligned, mask=mask_circle)
# img2에서 원 바깥쪽만 추출 (배경)
bg_circle = cv2.bitwise_and(image_2_aligned, image_2_aligned, mask=mask_circle_inv)
combined_circle = cv2.add(fg_circle, bg_circle) # 최종 합성
show_images_grid([
("원형 마스크", mask_circle),
("mask inverse", mask_circle_inv),
("image_1의 선택 영역", fg_circle),
("image_2의 배경 영역", bg_circle),
("최종 합성", combined_circle),
], cols=3, figsize=(15, 9))
ret, dst = cv2.threshold(
src, # 입력 그레이스케일 이미지 (single-channel 이미지)
thresh, # 임계값
maxval, # 임계값 이상일 경우 적용할 값
type # Threshold 타입, ex) cv2.THRESH_BINARY
)
주요 Threshold 타입
cv2.THRESH_BINARY : 픽셀값 ≥ 임계값 → maxval, 픽셀값 < 임계값 → 0cv2.THRESH_BINARY_INV : THRESH_BINARY의 반전cv2.THRESH_TRUNC : 픽셀값 > 임계값 → 임계값, 픽셀값 ≤ 임계값 → 픽셀값 그대로cv2.THRESH_TOZERO : 픽셀값 ≥ 임계값 → 픽셀값 그대로, 픽셀값 < 임계값 → 0cv2.THRESH_TOZERO_INV : THRESH_TOZERO의 반대if gray_image is not None:
# 임계값 설정
threshold_value = 127
max_value = 255
# (1) THRESH_BINARY - 픽셀 값이 127보다 크면 255, 작으면 0
# 픽셀값 >= 임계값 -> maxval, 픽셀값 < 임계값 -> 0
ret, thres_binary = cv2.threshold(gray_image, threshold_value, max_value, cv2.THRESH_BINARY)
print('[THRESH_BINARY]')
cv2_imshow(thres_binary)
# (2) THRESH_BINARY_INV - 픽셀 값이 127보다 크면 0, 작으면 255
# THRESH_BINARY 반전
ret, thres_binary_inv = cv2.threshold(gray_image, threshold_value, max_value, cv2.THRESH_BINARY_INV)
print('[THRESH_BINARY_INV]')
cv2_imshow(thres_binary_inv)
# (3) THRESH_TRUNC - 픽셀 값이 127보다 크면 127, 이하면 원래 값 유지
# 픽셀값 > 임계값 -> 임계값, 픽셀값 <= 임계값 -> 픽셀값 그대로
ret, thres_trunc = cv2.threshold(gray_image, threshold_value, max_value, cv2.THRESH_TRUNC)
print('[THRESH_TRUNC]')
cv2_imshow(thres_trunc)
# (4) THRESH_TOZERO - 픽셀 값이 127보다 크면 원래 값 유지, 이하면 0
# 픽셀값 >= 임계값 -> 픽셀값 그대로, 픽셀값 < 임계값 -> 0
ret, thres_tozero = cv2.threshold(gray_image, threshold_value, max_value, cv2.THRESH_TOZERO)
print('[THRESH_ZERO]')
cv2_imshow(thres_tozero)
# (5) THRESH_TOZERO_INV - 픽셀 값이 127보다 크면 0, 이하면 원래 값 유지
# THRESH_TOZERO의 반전
ret, thres_tozero_inv = cv2.threshold(gray_image, threshold_value, max_value, cv2.THRESH_TOZERO_INV)
print('[THRESH_TOZERO_INV]')
cv2_imshow(thres_tozero_inv)
else:
print("gray_image가 None입니다. 확인 후 다시 시도하세요.")
글씨나 조명이 고르지 않은 이미지에서는 글로벌한 임계값(고정된 임계값)만으로는 좋은 결과를 얻기 어렵다. 글로벌 임계값은 영상 전체에 하나의 고정된 threshold를 적용한다. 그러나 글씨나 문서 이미지에서 조명이 고르지 않으면 배경과 글씨의 밝기값이 위치에 따라 달라진다. 이 경우 어떤 영역에서는 배경과 글씨가 잘 구분되지만, 다른 영역에서는 배경이 지나치게 어둡거나 글씨가 흐려져 동일한 threshold로는 정확한 분리가 어렵다. 따라서 조명 변화가 큰 영상에서는 글로벌 threshold보다 영역별로 threshold를 다르게 적용하는 adaptive threshold가 더 적절하다. 적응형 임계점 처리를 활용하면 이미지를 작은 영역(블록)으로 나누어 각 영역에서 동적으로 임계값을 계산한다.
src : 입력 영상. 그레이스케일 영상maxValue : 임계값 함수 최댓값. 보통 255.adaptiveMethod : 블록 평균 계산 방법 지정. cv2.ADAPTIVE_THRESH_MEAN_C는 산술 평균, cv2.ADAPTIVE_THRESH_GAUSSIAN_C는 가우시안 가중치 평균thresholdType : cv2.THRESH_BINARY 또는 cv2.THRESH_BINARY_INV 지정block_size : 주변 픽셀의 영역 크기 (반드시 3 이상의 홀수)C : 실제 계산된 임계값에서 감산할 값 / 블록 내 평균값 또는 블록 내 가중 평균값에서 뺄 값. (x, y) 픽셀의 임계값으로 𝑇(𝑥, 𝑦) = 𝜇(𝑥, 𝑦 )− 𝐶 를 사용MEAN_C : blockSize 내의 픽셀 값 평균을 이용해 임계값을 결정GAUSSIAN_C : blockSize 내의 픽셀 값에 가우시안 가중치를 주어 평균을 계산dst = cv2.adaptiveThreshold(
src, # 그레이스케일 이미지
maxValue, # 임계값 이상일 때 적용할 값
adaptiveMethod, # (cv2.ADAPTIVE_THRESH_MEAN_C or cv2.ADAPTIVE_THRESH_GAUSSIAN_C)
thresholdType, # 보통 THRESH_BINARY 또는 THRESH_BINARY_INV
blockSize, # 주변 픽셀을 참고할 영역 크기 (홀수)
C # 평균이나 가우시안으로 계산된 임계값에서 빼줄 상수
)
ret, dst = cv2.threshold(
src, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
- Basic thresholding where you have to manually supply a threshold value, T
- Otsu’s thresholding, which automatically determines the threshold value
- 일반 threshold는 이미지 전체에 대해, adaptive threshold는 block 단위로 적용하므로 성능이 더 좋음. 다만, 후자는 연산량이 더 큼.
각 함수는 img 배열을 직접 수정합니다. 반환값을 다시 변수에 할당해도 되지만, img 자체가 변형됩니다.
cv2.line(img, start_point, end_point, color, thickness, lineType)img : 도형을 그릴 대상 이미지 (numpy array)start_point : 시작 좌표 (x, y)end_point : 끝 좌표 (x, y)color : BGR 튜플 (ex) (255, 0, 0)thickness : 선 두께lineType : 선의 형태 (ex. cv2.LINE_8, cv2.LINE_AA 등)cv2.LINE_4는 4-연결성을 이용하여 선을 그리는 방식으로, 상하좌우 방향 중심으로 픽셀이 연결되어 비교적 거친 선이 나타날 수 있다. cv2.LINE_8은 대각선 방향까지 포함하는 8-연결성을 사용하므로 더 자연스러운 선 표현이 가능하다. cv2.LINE_AA는 안티앨리어싱을 적용하여 경계 픽셀의 밝기를 조절함으로써 계단 현상을 줄이고 가장 부드러운 선을 만든다.# 직선 그리기 예제
image_line = np.full((512, 512, 3), 255, np.uint8) # 흰색 배경
image_line = cv2.line(
image_line,
(0, 0), # 시작 좌표
(255, 255), # 끝 좌표
(255, 0, 0), # 파란색 (BGR)
3 # 두께
)
show_image("Line Drawing", image_line)
cv2.rectangle(img, pt1, pt2, color, thickness)pt1 : 사각형의 한 꼭짓점 (x, y)pt2 : 대각선 반대쪽 꼭짓점 (x, y)# 사각형 그리기 예제
image_rect = np.full((512, 512, 3), 255, np.uint8)
image_rect = cv2.rectangle(
image_rect,
(20, 20), # 왼쪽 상단 꼭짓점
(255, 255), # 오른쪽 하단 꼭짓점
(255, 0, 0), # 파란색
3 # 선 두께
)
show_image("Rectangle Drawing", image_rect)
cv2.circle(img, center, radius, color, thickness)center : 원의 중심 좌표 (x, y)radius : 원의 반지름# 원 그리기 예제
image_circle = np.full((512, 512, 3), 255, np.uint8)
image_circle = cv2.circle(
image_circle,
(256, 256), # 중심
80, # 반지름
(255, 0, 0), # 파란색
3 # 선 두께
)
show_image("Circle Drawing", image_circle)
cv2.polylines(img, [points], isClosed, color, thickness)points : (N, 1, 2) 형태의 정수 numpy array (다차원 배열)isClosed : 닫힌 도형 여부 (True/False)isClosed=True로 설정하면 마지막 점과 첫 번째 점이 이어짐.# 다각형 그리기 예제
image_poly = np.full((512, 512, 3), 255, np.uint8)
# 점들을 정의 (x, y)
points = np.array([[50, 50], [150, 200], [300, 100], [400, 400]], np.int32)
points = points.reshape((-1, 1, 2)) # (N, 2) -> (N, 1, 2)
image_poly = cv2.polylines(
image_poly,
[points], # 리스트 형태로
True, # isClosed=True → 닫힌 도형
(0, 0, 255), # 빨간색
3
)
show_image("Polygon Drawing", image_poly)
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness)center : (x, y)axes : (major_axis_length/2, minor_axis_length/2) 즉 (가로축, 세로축)angle : 타원의 회전 각도 (도 단위) / 0이면 수평.startAngle , endAngle : 타원의 그릴 시작 각도와 끝 각도 / 0~360 전체 사용하면 완전한 타원cv2.ellipse(
img, center, axes,
angle, # 타원(회전각, 도 단위)
startAngle, # 시작 각도
endAngle, # 끝 각도
color, thickness
)
# 타원 그리기 예제
image_ellipse = np.full((512, 512, 3), 255, np.uint8)
image_ellipse = cv2.ellipse(
image_ellipse,
(256, 256), # 중심
(100, 50), # (가로축/2, 세로축/2)
30, # 회전 각도
0, 360, # 시작 각도, 끝 각도
(0, 255, 0), # 초록색
3 # 선 두께
)
show_image("Ellipse Drawing", image_ellipse)
cv2.putText(img, text, org, fontFace, fontScale, color, thickness)org : 텍스트 시작 좌표 (x, y)fontFace : 글꼴 (예: cv2.FONT_HERSHEY_SIMPLEX)fontScale : 글자 크기 (배율)cv2.putText(
img, text, org,
fontFace, fontScale,
color, thickness,
lineType
)
# 텍스트 추가 예제
image_text = np.full((512, 512, 3), 255, np.uint8)
image_text = cv2.putText(
image_text,
'Hello World', # 텍스트 내용
(50, 250), # 시작 위치 (x, y)
cv2.FONT_HERSHEY_SIMPLEX, # 폰트
1, # 크기 (배율)
(255, 0, 0), # 파란색
2 # 두께
)
show_image("Text Drawing", image_text)
frames 리스트에 저장imageio.mimsave() 함수를 사용하여 GIF 파일로 저장imageio.mimsave('output.gif', frames, fps=2)fps : 초당 프레임 수 (2 → 1초에 2프레임, 0.5초 간격)frames : RGB 형식의 numpy 배열로 구성# Colab에서 output.gif를 직접 표시
with open('output.gif','rb') as f:
display(Image(data=f.read()))
cv2.findContours()images : 8비트 단일 채널(Grayscale). 일반적으로 Threshold나 Canny 에지 검출 등으로 전처리가 된 이진화 이미지mode : 윤곽선을 찾는 방식 (어떤 윤곽선을 어떤 범위까지 찾을 것인가?)cv2.RETR_EXTERNAL : 가장 바깥쪽 윤곽선만 검색 (도넛의 경우 바깥 테두리는 찾지만, 안쪽 테두리는 무시) - 보통 물체 개수 셀 때 이용함.cv2.RETR_TREE : 모든 윤곽선을 검색하고 계층 구조를 형성 (바깥, 안쪽, 그 외 다른 경계를 모두 찾음) - 복잡한 형태 분석에 주로 사용함.method : 윤곽선 근사화 방식 (찾은 윤곽선을 어떤 방식으로 얼마나 자세히 저장할 것인가?)cv2.CHAIN_APPROX_NONE : 윤곽선 위에 있는 모든 픽셀들의 모든 좌표를 저장. 가장 자세히 표현, 요구 메모리/연산량 증가cv2.CHAIN_APPROX_SIMPLE : 직선 구간은 시작점과 끝점만 저장함으로써 메모리 절약contours : 윤곽선의 점들 좌표를 담고 있는 리스트hierarchy : 윤곽선의 계층 구조를 담고 있는 배열임.# 이진화된 이미지에서 경계선을 찾아서 contours를 반환 (Binarized image)
contours, hierarchy = cv2.findContours (
image, # 입력 이미지 (이진화된 형태가 일반적)
# 어떤 형태의 윤곽선을 찾을 건지
mode, # 윤곽선 검색 방법 (cv2.RETR_EXTERNAL, cv2.RETR_TREE 등)
# 찾은 윤곽선을 어떻게 저장할 것인지
method # 윤곽선 근사화 방법 (cv2.CHAIN_APPROX_SIMPLE 등)
)
cv2.drawContours()output = cv2.drawContours(
image, # 윤곽선을 그릴 대상 이미지 (BGR 컬러)
contours, # 찾은 윤곽선 리스트
contouridx, # -1이면 모든 윤곽선을 그림
color, # BGR
thickness # 선 두께
)
Contour와 Edge의 차이
- Contour는 객체 중심, 외곽선을 모두 딴 것 (binary object에서 전체 boundary 경계선 따는 거)
- 닫힌 형태 / 전처리가 안 된 경우 불완전하게 표현되기도 함.
- 컬러 이미지 자체를 보면 색상에 따라서 판단 기준이 모호해지므로 Grayscale/Binary 이미지로 변환해서 명확히 봄.
- Edge는 밝기 변화가 큰 지점을 찾는 것
+---------+
| /---- |
| / obj |
| /---- |
+---------+
x, y, w, h = cv2.boundingRect(contour)
- (x, y)는 사각형의 왼쪽 위 꼭짓점 좌표
- (w, h)는 사각형 너비와 높이
- 직교된 사각형(Axis-Aligned Bounding Box)이므로 이미지 축에 평행함.
hull = cv2.convexHull(contour)
cv2.drawContours(hull, [hull], -1, (0, 0, 255), 2)
approx = cv2.approxPolyDP(contour, epsilon, closed)
# Approximate the contour to a polygon
epsilon = 0.02 * cv2.arcLength(contour, True) # 2% of the contour perimeter
approx = cv2.approxPolyDP(contour, epsilon, True)
print("Number of points in original contour : ", len(contour))
print("Number of points in approximated polygon : ", len(approx))
# Draw the approximated polygon in yellow
output_approx = output.copy()
cv2.drawContours(output_approx, [approx], -1, (0, 255, 255), 2)
show_image("Approximated Polygon", output_approx)
cv2.arcLength(contour)cv2.arcLength(contour, closed=True)cv2.moments(contour)M = cv2.moments(contour)라 하면,cx = moments['m10'] / moments['m00']cy = moments['m01'] / moments['m00']# Contour basic info
area = cv2.contourArea(contour)
perimeter = cv2.arcLength(contour, True)
moments = cv2.moments(contour)
print("Contour Area:", area)
print("Contour Perimeter:", perimeter)
print("Moments:", moments)
# Calculate centroid using moments
if moments['m00'] != 0:
cx = int(moments['m10'] / moments['m00'])
cy = int(moments['m01'] / moments['m00'])
print(f"Centroid: ({cx}, {cy})")
else:
print("Contour area is zero, cannot compute centroid.")
# rect 는 ( (cx, cy), (width, height), angle ) 형태로 반환
# cv2.boxPoints(rect) 를 이용해 사각형 4점의 좌표를 얻을 수 있음.
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect)
box = np.int0(box)