import cv2
img = cv2.imread('images/sun.jpg')
x = 182
y = 22
w = 119
h = 108
roi = img[y:y+h, x:x+w] # y:y+h는 세로 방향, x:x+w는 가로 방향
roi_copy = roi.copy() # ROI를 복사
img[y:y+h, x+w:x+w+w] = roi
# 두 태양을 박스로 감싸기
cv2.rectangle(img, (x, y), (x+w+w, y+h), (0, 255, 0), 3)
cv2.imshow('img', img)
cv2.waitKey()
import cv2
oldx = oldy = w = h = 0
color = (255, 0, 0)
isDrag = False
img_copy = None
def on_mouse(event, x, y, flags, param):
global oldx, oldy, w, h, isDrag, img_copy
if event == cv2.EVENT_LBUTTONDOWN:
isDrag = True
oldx = x
oldy = y
elif event == cv2.EVENT_MOUSEMOVE:
if isDrag:
img_copy = img.copy()
cv2.rectangle(img_copy, (oldx, oldy), (x, y), color, 3)
cv2.imshow('img', img_copy)
elif event == cv2.EVENT_LBUTTONUP:
if isDrag:
isDrag = False
if x > oldx and y > oldy:
w = x - oldx
h = y - oldy
if w > 0 and h > 0:
cv2.rectangle(img_copy, (oldx, oldy), (x, y), color, 3)
cv2.imshow('img', img_copy)
roi = img[oldy:oldy+h, oldx:oldx+w]
cv2.imshow('roi', roi)
else:
cv2.imshow('img', img)
print('영역이 설정되지 않음')
img = cv2.imread('./images/sun.jpg')
cv2.namedWindow('img')
cv2.setMouseCallback('img', on_mouse)
cv2.imshow('img', img)
cv2.waitKey()
이렇게 박스를 그리면 박스 안 영역 이미지만 추출해낼 수 있다.
영상을 흑백(0 또는 255)으로 변환하여
특정 임계값(threshold) 이상인 픽셀을 흰색(255)으로, 이하인 픽셀을 검은색(0)으로 변환하는 과정.
OpenCV에서는 cv2.threshold() 함수를 사용하여 고정 임계값 이진화, 적응형 이진화, Otsu의 이진화 등을 적용할 수 있다.
기본적인 이진화 방법 : cv2.THRESH_BINARY
조명이 균일하지 않은 경우에도 효과적인 이진화 방법 : cv2.ADAPTIVE_THRESH_GAUSSIAN_C
cv2.THRESH_OTSU 옵션을 사용하여 cv2.threshold() 함수에서 적용할 수 있다. cv2.THRESH_BINARY + cv2.THRESH_OTSU를 함께 사용하여 이진화를 수행하며, 그레이스케일 변환이 선행되어야 한다.# 이진화
# 이미지의 픽셀 값을 0과 1(0과 255) 두 가지 값만 가지도록 만드는 영상 처리 기법
# OCR, 윤곽 검출, 객체 분할, 문서 스캔 등 작업에 유리
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('./images/cells.png', cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
# 픽셀값이 임계값을 넘으면 최대값으로 설정하고, 넘지 못하면 0으로 설정
a, dst1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)
print(a) # 임계값
b, dst2 = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY)
print(b) # 임계값
c, dst3 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print(c) # Otsu 임계값
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
plt.plot(hist)
plt.show()
cv2.waitKey()
100.0 # dst1 임계값
210.0 # dst2 임계값
206.0 # dst3 (Otsu 임계값)
cv2.threshold()는 하나의 고정된 임계값을 사용하지만, cv2.adaptiveThreshold()는 영상의 작은 영역마다 서로 다른 임계값을 계산하여 적용한다.cv2.ADAPTIVE_THRESH_MEAN_C와 가우시안 가중치를 적용하는 cv2.ADAPTIVE_THRESH_GAUSSIAN_C 두 가지 방식을 제공하며, 블록 크기(blockSize)와 상수 값(C)을 조절하여 최적의 결과를 얻을 수 있다.import cv2
import numpy as np
img = cv2.imread('./images/sudoku.jpg', cv2.IMREAD_GRAYSCALE)
# 전역 자동 이진화
a, dst1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 지역 자동 이진화
dst2 = np.zeros(img.shape, dtype=np.uint8)
bw = img.shape[1] // 4
bh = img.shape[0] // 4
# 적응형 이진화
# ADAPTIVE_THRESH_MEAN_C: 해당 픽셀 주변의 평균값을 기준으로 임계값 설정
# 9: 이웃 블록 크기. 현재 픽셀 주변에서 얼마만큼의 영역을 고려할지 설정. 클수록 더 넓은 영역을 평균 -> 조명에 덜 민감하지만 부드러움
# 5: 임계값 보정 상수, 평균 또는 가중 평균에서 얼마나 빼줄지 결정. 값이 클수록 픽셀이 어두워야 흰색이 됨(임계값을 낮추거나 높이기 위한 튜닝용 파라미터)
dst3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5)
# ADAPTIVE_THRESH_GAUSSIAN_C: 주변 픽셀에 가중치를 곱한 평균값으로 임계값을 설정
# 조명이 불균일한 이미지에서 효과적, 부드러운 결과
dst4 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 5)
for y in range(4):
for x in range(4):
img_ = img[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
dst_ = dst2[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
cv2.threshold(img_, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU, dst_)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.imshow('dst4', dst4)
cv2.waitKey()
이렇게 조명에 따라 밝기가 다르게 찍힌 이미지에서 전역 자동 이진화를 하게 되면 검게 출력되는 부분이 있음을 확인했다.
조명에 따라 밝기가 다르게 찍힌 이미지에서는 지역 자동 이진화나 적응형 이진화를 해야 함을 확인할 수 있었다.
영상의 형태, 크기, 밝기, 색상 등을 변경하여 새로운 영상으로 변환하는 과정.
컴퓨터 비전에서 다양한 전처리 및 후처리에 활용된다.
기하학적 변환(Geometric Transformation)
확대/축소(Scaling), 회전(Rotation), 이동(Translation), 투시 변환(Perspective Transform) 등
cv2.warpAffine(), cv2.getPerspectiveTransform()
강도 변환(Intensity Transformation)
픽셀 값의 변화를 조정하는 방법
명암 조절, 히스토그램 평활화, 이진화(Thresholding) 등
cv2.equalizeHist(), cv2.threshold(), cv2.adaptiveThreshold() 등
import cv2
import numpy as np
img = cv2.imread('./images/dog.bmp')
aff = np.array([
[1, 0, 150],
[0, 1, 100]
], dtype=np.float32) # # 원본 이미지를 오른쪽으로 150픽셀, 아래로 100픽셀 이동시키는 변환
# 어파인 변환
# 이미지의 위치나 모양을 변경하는 선형 변환
# (0, 0) : 원본 이미지 크기를 그대로 전달
dst1 = cv2.warpAffine(img, aff, (0, 0))
# interpolation(보간법): 픽셀을 어떻게 채울지 결정
# INTER_NEAREST : 최근법 이웃 보간(속도 빠름, 품질 낮음), 가까운 픽셀 값을 그대로 복사. 계단 현상이나 노이즈가 생길 수 있음
dst2 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
# INTER_CUBIC : 4차 보간법(속도 느림, 품질 좋음), 주변 16개 픽셀을 사용하여 곡선으로 예측, 이미지를 부드럽게 확대/축소
dst3 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_CUBIC)
cp = (img.shape[1] / 2, img.shape[0] / 2) # (가로 중앙, 세로 중앙) = 이미지의 중심 좌표(center point)
rot = cv2.getRotationMatrix2D(cp, 30, 0.7)
dst4 = cv2.warpAffine(img, rot, (0, 0))
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.imshow('dst4', dst4)
cv2.waitKey()
cv2.getRotationMatrix2D(center, angle, scale)
- center: 회전 기준점 (여기서는 이미지의 중앙)
- angle: 회전 각도 (양수는 반시계 방향 회전)
- scale: 크기 조절 계수 (< 1은 축소)
angle=30, scale=0.7이면
중심 기준으로 30도 반시계 방향 회전
70% 크기로 축소
import cv2
import numpy as np
img = cv2.imread('./images/pic.jpg')
w, h = 600, 400
srcQuad = np.array(
[[370, 173], [1224, 157], [1417, 830], [209, 849]], np.float32
)
dstQuad = np.array(
[[0, 0], [w, 0], [w, h], [0, h]], np.float32
)
# 투시 변환(Perspective Transform)
# 영상에서 원근감을 조절하거나, 사각형을 반듯하게 펴주는 변환
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(img, pers, (w, h))
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
이미지 좌표는 그림판에서 확인해볼 수 있다.
import cv2
import numpy as np
import sys
img = cv2.imread('./images/namecard.jpg')
h, w = img.shape[:2]
dh = 500
# A4용지 크기: 210mm*297mm
dw = round(dh * 297 / 210)
srcQuad = np.array([
[30, 30], [30, h-30], [w-30, h-30], [w-30, 30]
], np.float32)
dstQuad = np.array([
[0, 0], [0, dh], [dw, dh], [dw, 0]
], np.float32)
dragSrc = [False, False, False, False]
ptOld = 0
def drawROI(img, corners):
cpy = img.copy()
c1 = (192, 192, 255)
c2 = (128, 128, 255)
for pt in corners:
cv2.circle(cpy, tuple(pt.astype(int)), 25, c1, -1)
cv2.line(cpy, tuple(corners[0].astype(int)), tuple(corners[1].astype(int)), c2, 2)
cv2.line(cpy, tuple(corners[1].astype(int)), tuple(corners[2].astype(int)), c2, 2)
cv2.line(cpy, tuple(corners[2].astype(int)), tuple(corners[3].astype(int)), c2, 2)
cv2.line(cpy, tuple(corners[3].astype(int)), tuple(corners[0].astype(int)), c2, 2)
return cpy
def onMouse(event, x, y, flags, param):
global srcQuad, dragSrc, ptOld
if event == cv2.EVENT_LBUTTONDOWN:
for i in range(4):
if cv2.norm(srcQuad[i] - (x, y)) < 25:
dragSrc[i] = True
ptOld = (x, y)
break
if event == cv2.EVENT_MOUSEMOVE:
for i in range(4):
if dragSrc[i]:
srcQuad[i] = (x, y)
cpy = drawROI(img, srcQuad)
cv2.imshow('img', cpy)
ptOld = (x, y)
break
if event == cv2.EVENT_LBUTTONUP:
for i in range(4):
dragSrc[i] = False
disp = drawROI(img, srcQuad)
cv2.namedWindow('img')
cv2.setMouseCallback('img', onMouse)
cv2.imshow('img', disp)
while True:
key = cv2.waitKey()
if key == 27: # ESC 키 누르면 종료
sys.exit()
elif key == 13: # Enter 키 누르면 결과 확인
break
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(img, pers, (dw, dh), flags=cv2.INTER_CUBIC)
# cv2.INTER_CUBIC : 4x4 주변 픽셀을 이용한 고급 보간법 (느리지만 부드러움)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.blur(), cv2.GaussianBlur(), cv2.medianBlur() 함수를 통해 적용할 수 있다. cv2.Sobel(), cv2.Laplacian()을 사용하여 적용할 수 있다.import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('./images/dog.bmp')
# img = cv2.imread('./images/noise.bmp')
# img = cv2.imread('./images/gaussian_noise.jpg')
dst1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst2 = cv2.blur(img, (7, 7))
# GaussianBlur
# 픽셀 주변 값들을 가우시안 분포에 따라 가중 평균에서 흐리게 만듬
# 커널 크기가 클수록 넓은 범위를 흐리게 함
# 전체적으로 흐림 처리만 하고 싶을 때
dst3 = cv2.GaussianBlur(img, (0, 0), 2)
# 픽셀 주변의 값 중에서 중간값을 선택해서 새로운 픽셀 값으로 바꾸는 필터
# 이미지의 각 픽셀을 기준으로 주변 픽셀을 확인한 후 그 중에서 가장 중간값(순서대로 정렬)을 가져와서 그 픽셀을 새로운 값으로 바꿔 블러 처리 함
dst4 = cv2.medianBlur(img, 7)
# 양방향 필터(bilateralFilter)
# 엣지(윤곽선) 보존하면서 부드럽게 처리할 수 있는 필터
# 공간 정보 + 픽셀 색상 정보를 함께 고려
# 색상 거리 시그마, 공간 거리 시그마
# 노이즈를 줄이고, 윤곽선을 유지하고 싶을 때
dst5 = cv2.bilateralFilter(img, 12, 100, 100)
# Canny 엣지 검출
# 엣지를 찾기 위한 임계값을 자동으로 조절하여 엣지를 찾아주는 알고리즘
# lower threshold: 약한 엣지를 설정
# upper threshold: 강한 엣지를 설정
med_val = np.median(img)
lower = int(max(0, 0.7 * med_val))
upper = int(min(255, 1.3*med_val))
print(lower)
print(upper)
dst6 = cv2.GaussianBlur(img, (3, 3), 0)
dst6 = cv2.Canny(dst6, lower, upper, 3)
cv2.imshow('img', img)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.imshow('dst4', dst4)
cv2.imshow('dst5', dst5)
cv2.imshow('dst6', dst6)
plt.figure(figsize=(10, 5))
for i, k in enumerate([5, 7, 9]):
# k * k 크기 커널 생성: 모든 원소가 1/k**2 예) k=5 1/25 로 채워진 5*5 행렬
kernel = np.ones((k, k)) / k ** 2
# dst1: 영상, -1: 출력 이미지의 깊이(채널 수 등)를 입력 이미지와 동일하게 유지
filtering = cv2.filter2D(dst1, -1, kernel)
plt.subplot(1, 3, i+1)
plt.imshow(filtering)
plt.title('kernel size: {}'.format(k))
plt.axis('off')
plt.show()
cv2.waitKey()
cv2.GaussianBlur(src, ksize, sigmaX)
| 인자 | 의미 |
|---|---|
src | 원본 이미지 (img) |
ksize | 블러 처리에 사용할 커널 크기 → (width, height) |
sigmaX | X방향 표준편차 (σ), 즉 블러 강도 |
| 반환값 | 블러가 적용된 이미지 |
ksize를 (0, 0)으로 설정하면, OpenCV가 sigmaX 값(여기선 2)을 기준으로 적절한 커널 크기를 자동으로 계산한다.
cv2.medianBlur(img, 7)
"소금-후추 노이즈(salt-and-pepper noise)" 제거에 특히 강하다.
cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace [, dst [, borderType]]) [] : 생략 가능