728x90
1. 관심 영역(ROI, Region of Interest)
- 영상 내에서 관심이 있는 영역
✔️ ROI 추출, 복사하고 그 영역을 녹색 사각형으로 강조하기
import cv2
img = cv2.imread('./sun.jpg')
# ROI 영역의 좌표와 크기 설정
x = 182
y = 21
w = 120
h = 110
# ROI 영역 설정
roi = img[y:y+h, x:x+w]
roi_copy = roi.copy() # copy()는 원본에 영향을 끼치지 않음
# 원본 이미지의 오른쪽에 ROI 영역 복사
img[y:y+h, x+w:x+w+w] = roi
# 복사된 ROI 영역 주위에 녹색 사각형 그리기
cv2.rectangle(img, (x, y), (x+w+w, y+h), (0, 255, 0), 3)
cv2.imshow('img', img)
cv2.imshow('roi_copy', roi_copy)
cv2.waitKey()
✔️ ROI 직접 드래그하고 Enter키 눌러 추출하기(selectROI())
import cv2
img = cv2.imread('./sun.jpg')
# ROI 선택(윈도우 상에서 직접 드래그하여 선택)
x, y, w, h = cv2.selectROI('img', img, False) # False: Cross 표기 여부
# 선택한 ROI가 유효한 경우
if w and h:
roi = img[y: y+h, x: x+w]
cv2.imshow('roi', roi)
cv2.waitKey()
✔️ Enter키 누르지 않고 바로 ROI 추출
import cv2
oldx = oldy = w = h = 0 # 마우스 드래그 시작 위치, 드래그한 영역의 너비와 높이
color = (255, 0, 0) # 사각형 색상
img_copy = None # 드래그 중 사각형을 그리기 위한 이미지 복사본
isDrag = False # 드래그 중인지 여부
# 마우스 이벤트 처리
def on_mouse(event, x, y, flags, param):
global oldx, oldy, w, h, color, img_copy, isDrag
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('./sun.jpg')
cv2.imshow('img', img)
cv2.setMouseCallback('img', on_mouse)
cv2.waitKey()
2. 영상의 이진화(Binarization)
- 픽셀을 검은색 또는 흰색과 같이 두 분류의 값으로 나누는 작업
- 영상에서 의미있는 관심 영역(ROI)과 비관심 영역으로 구분할 때 사용
- 예) 배경과 객체를 둘로 나눌 때
- 영상의 이진화 연산을 할 때 나누는 특정값을 임계값이라고 함
- cv2.threshold(영상, 임계값, 최댓값, 플래그)
- cv2.THRESH_BINARY: 픽셀값이 임계값을 넘으면 최댓값으로 지정하고 넘지 못하면 0으로 지정
- 예) 픽셀값 150, 임계값 120: 픽셀값은 255로 설정
- cv2.THRESH_BINARY_INV: cv2.THRESH_BINARY의 반대
✔️ 이진화(thresholding)
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('./cells.png', cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([img], [0], None, [256], [0, 255]) # 이미지의 밝기 값에 대한 히스토그램 계산
# [256]: 밝기 값의 범위, [0,255]까지의 값이 계산됨
# THRESH_BINARY: 임계값보다 큰 픽셀 값을 255, 작은 픽셀 값을 0으로 설정
a, dst1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY) # 임계값: 100, 픽셀 값을 0 또는 255로 변환
b, dst2 = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY) # 임계값: 210
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
plt.plot(hist)
plt.show()
cv2.waitKey()
3. 오츠의 이진화 알고리즘
- 자동 이진화 알고리즘
- 자동으로 임계값을 구해줌(임계값을 구분하는 가장 좋은 방법으로 찾아줌)
- cv2.threshold(영상, 임계값, 최댓값, 플래그 | cv2.THRESH_OTSH)
- 임계값을 임의로 정해 픽셀을 두 부류로 나누고 두 부류의 명암 분포를 구하는 작업을 반복하여 모든 경우의 수 중에서 두 부류의 명암 분류가 가장 균일할 때의 임계값을 선택
✔️ 오츠의 자동 이진화 알고리즘
import cv2
img = cv2.imread('./rice.png', cv2.IMREAD_GRAYSCALE)
# 0: 임계값을 지정하지 않음, 255: 이진화된 이미지에서의 최대 픽셀 값
# 오츠의 알고리즘을 추가하여 임계값을 자동으로 결정하게 함
# th: 오츠의 알고리즘으로 계산된 임계값, dst: 이진화된 이미지
th, dst = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print('otsh: ', th)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
4. 지역 이진화
- 균일하지 않은 조명 환경에서 사용하는 이진화 방법
- 전체 구역을 N등분하고 각각의 구역에 이진화 한 뒤에 이어 붙이는 방법
- 여러 개의 임계값을 이용할 수 있음
✔️ 이미지 전체 이진화 vs 지역 이진화
import cv2
import numpy as np
img = cv2.imread('./rice.png', cv2.IMREAD_GRAYSCALE)
_, dst1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 이진화된 부분 이미지들을 결합할 빈 이미지, 원본 크기를 가지며 초기 값은 모두 0
dst2 = np.zeros(img.shape, np.uint8)
# 이미지의 가로와 세로를 4등분한 블록의 너비(bw)와 높이(bh)
bw = img.shape[1] // 4
bh = img.shape[0] // 4
# 4x4 블록으로 나누어 부분 이진화
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.waitKey()
5. 적응형 이진화
- 영상을 여러 영역으로 나눈 뒤, 그 주변 픽셀 값만 활용하여 임계값을 구하는 방법
- cv2.adaptiveThreshold()
- cv2.ADAPTIVE_THRESH_MEAN_C
- 이웃 픽셀의 평균으로 결정
- 선명하지만 잡티가 많아질 가능성이 있음
- 문서 스캔과 같이 이미지가 고르게 조명된 경우에 효과적
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C
- 가우시안 분포에 따른 가중치의 합으로 결정
- 선명도는 조금 떨어지지만 잡티가 적음
- 조명이 불균일한 이미지나 디테일을 유지하고자 할 때 효과적
- cv2.ADAPTIVE_THRESH_MEAN_C
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('./sudoku.jpg', cv2.IMREAD_GRAYSCALE)
# 오츠의 이진화 알고리즘
th, dst1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# adaptiveThreshold(): 작은 영역마다 이진화 적용
# 255: 이진화된 이미지에서 흰색으로 변환할 최대값
# ADAPTIVE_THRESH_MEAN_C: 주변 픽셀들의 평균값을 기준으로 임계값 설정
# ADAPTIVE_THRESH_GAUSSIAN_C: 가우시안 가중 평균을 기준으로 임계값 설정
# 9: 블록 크기, 5: 계산된 임계값에서 빼는 상수
dst2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5)
dst3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 5)
dic = {'img': img, 'dst1': dst1, 'dst2':dst2, 'dst3': dst3}
for i, (k, v) in enumerate(dic.items()):
plt.subplot(2, 2, i+1)
plt.title(k)
plt.imshow(v, 'gray')
plt.show()
6. 영상의 변환
- 구성하는 픽셀의 배치 구조를 변경함으로 전체 영상의 모양을 바꾸는 작업
- 이미지 이동(translate)
- 변환행렬
- M= [ 1 0 a ] x방향으로 a만큼
- [ 0 1 b ] y방향으로 b만큼
- cv2.warpaffine(영상, aff행렬, (0, 0)) : 입력 영상과 크기가 같은 행렬을 반환한다
import cv2
import numpy as np
img = cv2.imread('./dog.bmp')
# x축으로 200픽셀, y축으로 100픽셀 평행 이동
aff = np.array([[1, 0, 200], [0, 1, 100]], dtype=np.float32)
dst = cv2.warpAffine(img, aff, (0, 0)) # 주어진 2x3 변환행렬(aff)을 사용하여 이미지 변환
# (0, 0)은 출력 이미지의 크기를 지정하는 부분, 기본적으로 원본 이미지 크기와 동일하게 처리됨
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
- 크기 변환(resize)
- 영상의 크기를 원본 영상보다 크게 또는 작게 만드는 변환
- cv2.resize(영상, 크기, 보간법)
- 보간법 알고리즘
- cv2.INTER_LINEAR: 인접한 4개의 픽셀 값에 거리 가중치를 사용(속도는 빠르지만 퀄리티가 조금 떨어짐)
- cv2.INTER_NEAREST: 가장 가까운 픽셀 값을 사용(속도는 빠르지만 퀄리티가 조금 떨어짐)
- cv2.INTER_CUBIC: 인접한 16개의 픽셀 값에 가중치를 사용(퀄리티는 가장 높지만 속도가 떨어짐)
- cv2.INTER_AREA: 픽셀 영역 관계를 이용한 재샘플링(영역적인 정보를 추출해서 영상을 다시 세팅하기 때문에 다운 샘플링시 효과적)
import cv2
img = cv2.imread('./dog.bmp')
dst1 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
dst2 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1[200:500, 200:500]) #이미지의 (200, 200)부터 (500, 500)까지의 영역을 보여줌
cv2.imshow('dst2', dst2[200:500, 200:500])
cv2.waitKey()
- 회전(rotation)
- 영상을 특정 각도만큼 회전시키는 변환(반시계 방향, 음수는 시계 방향)
- cv2.getRotatioinMatrix2D(중심좌표, 회전각도, 확대비율) -> affine 행렬
- cv2.warpAffine(영상, affine행렬, (0, 0))
import cv2
img = cv2.imread('./dog.bmp')
# 중심점 계산, cp: 회전의 중심점
cp = (img.shape[1] / 2, img.shape[0] / 2) # 가로/2, 세로/2
# 회전 변환 행렬 생성, 30: 회전 각도(시계 반대 방향), 0.7: 스케일링 인자 -> 30도 회전, 크기는 70%로 축소
rot = cv2.getRotationMatrix2D(cp, 30, 0.7)
# 이미지 변환 행렬 rot을 사용하여 이미지 회전하고 스케일링한 결과를 dst에 저장
dst = cv2.warpAffine(img, rot, (0, 0)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
- 투시 변환(perspective)
- 직사각형 형태의 영상을 임의의 입체감 있는 사각형 형태로 변환
- 원본 영상에 있는 직선은 결과 영상에서 그대로 유지 되지 않고 평행 관계가 깨질 수 있음
- 투시 변환은 보통 3*3 크기의 실수 행렬로 표현
- cv2.getPerspectiveTransform(영상, 4개의 결과 좌표점) -> 투시변환 행렬
- cv2.warpPerspective(영상, 투시변환행렬, 결과영상크기)
import cv2
import numpy as np
img = cv2.imread('./pic.jpg')
w, h = 600, 400 # 출력 이미지의 폭과 높이
# 원본 이미지에서 변환할 사각형의 4개의 좌표
srcQuad = np.array([[369, 172], [1228, 156], [1424, 846], [207, 801]], np.float32)
# 출력 이미지에서 목표로 하는 4개의 좌표
dstQuad = np.array([[0, 0], [w, 0], [w, h], [0, h]], np.float32)
# 원근 변환 행렬 계산: 원본 이미지의 사각형을 새로운 사각형으로 변환하기 위함
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
# 원근 변환 적용
dst = cv2.warpPerspective(img, pers, (w, h))
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
💡문제
- 직사각형 이미지를 정렬하고, 왜곡된 이미지를 바로 잡아라.
- 이미지를 읽어와서 초기 사각형 영역을 설정한다.
- 마우스를 사용해 4개의 코너를 드래그하여 원하는 위치로 조정한다.
- 엔터키를 누르면 설정된 코너에 맞춰 원근 변환이 적용되고, 결과 이미지를 보여준다.
- ESC키를 누르면 프로그램이 종료된다.
import cv2
import numpy as np
import sys
# 이미지의 코너 지점을 시각적으로 표시하는 함수
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
# 마우스 이벤트 처리 함수
ptOld = None
def onMouse(event, x, y, flags, parma):
global arcQuad, dragSrc, ptOld, img
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_LBUTTONUP: # 왼쪽 버튼을 뗐을 때
for i in range(4):
dragSrc[i] = False
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
img = cv2.imread('./namecard.jpg')
h, w = img.shape[:2]
dh = 500 # 출력 이미지의 높이
dw = round(dh * 297 / 210) # 출력 이미지의 폭(A4비율)
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, False]
# ROI 표시 및 마우스 콜백 설정
disp = drawROI(img, srcQuad)
cv2.imshow('img', disp)
cv2.setMouseCallback('img', onMouse)
# 원근 변환 및 결과 출력
while True:
key = cv2.waitKey()
if key == 13: # 엔터키 눌러서 코너 설정 완료하면, 원근 변환 행렬 구하고 이미지 변환
break
elif key == 27: # ESC키
sys.exit()
pers = cv2.getPerspectiveTransform(srcQuaad, dstQuad)
dst = cv2.warpPerspective(img, pers, (dw, dh), flags=cv2.INTER_CUBIC)
cv2.imshow('dst', dst)
cv2.waitKey()
728x90
'딥러닝' 카테고리의 다른 글
[딥러닝] 블러링(Blurring), 엣지(edge) 검출, 모폴로지(morphology), 레이블링(labeling), 외곽선(contours) (1) | 2024.08.23 |
---|---|
[딥러닝] 평활화, 정규화, YCrCb, HSV, CLAHE, inRange(), copyTo() (0) | 2024.08.20 |
[딥러닝] 키이벤트, 마우스이벤트, 화소처리, 히스토그램 (2) | 2024.08.19 |
[딥러닝] 영상(Image), OpenCV-Python, 동영상 다루기 (0) | 2024.08.16 |
9. 전이 학습 (0) | 2024.07.31 |