1. 마스크 연산
inRange() : 영상에서 지젇된 범위 안에 픽셀 선택
copyTo() : 마스크 연산을 지원하는 픽셀 값 복사 함수
◼ 특정 범위의 픽셀 값을 추출
cv2.inRange(영상, min값 (Hue, Saturation, Value) , max값 (Hue, Saturation, Value) ) |
예시) cv2.inRange(img, (50, 150, 0), (80, 255, 255)) Hue(색조)가 50에서 80 사이, Saturation(채도)가 150에서 255 사이, Value(명도)가 0에서 255 사이의 범위를 지정합니다. |
◼ 특정 HSV 색상 범위를 추출하여 이진 이미지를 생성
import cv2
img = cv2.imread('./candies.png')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
dst = cv2.inRange(hsv, (50, 150, 0), (80, 255, 255))
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
|
◼ 마스크를 사용하여지정된 부분을 출력 영상으로 복사
cv2.copyTo(영상, 마스크, 출력영상) |
◼ 마스크 따기
import cv2
img = cv2.imread('./airplane.bmp')
mask = cv2.imread('./mask_plane.bmp')
dst = cv2.imread('./field.bmp')
cv2.imshow('img', img)
cv2.imshow('mask', mask)
cv2.waitKey()
|
◼ 이미지 합치기
import cv2
img = cv2.imread('./airplane.bmp')
mask = cv2.imread('./mask_plane.bmp')
dst = cv2.imread('./field.bmp')
# dst가 없으면 새로 만들고
# dst가 있으면 dst에 추가
temp = cv2.copyTo(img, mask)
cv2.copyTo(img, mask, dst)
cv2.imshow('img', img)
cv2.imshow('mask', mask)
cv2.imshow('dst', dst)
cv2.imshow('temp', temp)
cv2.waitKey()
|
◼ 영상에 크로마키로 다른 배경을 넣어보기
import cv2
cap1 = cv2.VideoCapture('./woman.mp4')
cap2 = cv2.VideoCapture('./sea.mp4')
w = round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
fps = round(cap1.get(cv2.CAP_PROP_FPS))
print(w)
print(h)
print(frame_cnt1)
print(frame_cnt2)
print(fps)
while True:
ret1, frame1 = cap1.read()
if not ret1:
break
ret2, frame2 = cap2.read()
if not ret2:
break
hsv = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (50, 150, 0), (70, 255, 255))
cv2.copyTo(frame2, mask, frame1)
cv2.imshow('frame1', frame1)
key = cv2.waitKey(10)
if key == ord(' '):
cv2.waitKey()
elif key == 27:
break
cap1.release()
cap2.release()
|
2. 관심 영역 (ROI, Region of Interest)
- 영상 내에서 관심이 있는 영역
◼ 관심 영역을 선택하기
cv2.selectROI(창이름, 영상, 중안좌표여부 = False) |
|
◼ 관심 영역을 추출하기
import cv2
img1 = cv2.imread('./sun.jpg')
# x, y, w, h
x = 182
y = 21
w = 122
h = 110
roi = img1[y: y+h, x: x+w] # 이미지에서 ROI 영역을 슬라이싱
img2 = roi.copy() # ROI 영역을 새로운 이미지로 복사
cv2.imshow('img1', img1) # 원본 이미지
cv2.imshow('img2', img2) # ROI 이미지
cv2.waitKey()
|
◼ 드래그한 곳을 추출하기
import cv2
isDrag = False
oldx = oldy = w = h = 0
color = (255, 0, 0)
img_copy = None
def on_mouse(event, x, y, flags, param):
global oldx, oldy, 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('./sun.jpg')
cv2.namedWindow('img')
cv2.setMouseCallback('img', on_mouse)
cv2.imshow('img', img)
cv2.waitKey()
|
◼ cv2.selectROI 함수를 사용해보기
import cv2
img = cv2.imread('./sun.jpg')
x, y, w, h = cv2.selectROI('img', img, False)
if w and h:
roi = img[y: y+h, x: x+w]
cv2.imshow('roi', roi)
cv2.waitKey()
|
1. 영상의 이진화(Binarization)
- 픽셀을 검은색 또는 횐색과 같이 두 개의 값으로 나누는 작업
- 영상에서 의미있는 관심영역(ROI)과 비 관심영역 구분할 때 사용
- 배경과 객체를 나울 때도 사용
- 영상의 이진화 연산을 할 때 나누는 특정값을 임계값이라고 함
◼ 이미지의 이진화 처리
cv2.threshold(영상, 임계값, 최대값, 플래그) cv2.THRESH_BINARY: 픽셀값이 임계값을 넘으면 최대값으로 지정하고 넘지 못하면 0으로 지정 cv2.THRESH_BINARY_INV: THRESH_BINARY 의 반대 |
◼ 예제 : 흑백이미지 이진화 처리
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('./cells.png', cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# cv2.THRESH_BINARY: 픽셀값이 임계값을 넘으면 최대값으로 지정하고 넘지 못하면 0으로 지정
a, dst1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)
b, dst2 = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY)
print('a:', a)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
plt.plot(hist)
plt.show()
cv2.waitKey()
|
2. 오츠의 이진화 알고리즘
- 자동 이진화
- 자동으로 임계값을 구하는 알고리즘, 임계값을 구분하는 가장 좋은 방법으로 사용
◼ Otsu's 방법을 통해 자동으로 최적의 임계값을 선택
cv2.threshold(영상, 임계값, 최대값, 플래그 | cv2.THRESH_OTSU) |
- 임계값을 임의로 정해 픽셀을 두 분류로 나누고 두 분류의 명암 분포를 구하는 작업을 반복하여 모든 경우의 수 중에서 두 분류의 명암 분류가 가장 균일할 때의 임의값을 선택 |
◼ 예제 : Otsu's 방법을 통해 이진화
import cv2
img = cv2.imread('./rice.png', cv2.IMREAD_GRAYSCALE)
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()
|
3. 지역 이진화
- 균일하지 않은 조명 환경에서 사용하는 이진화 방법
- 전체 구역에 N등분하고 각각의 디역에 이진화를 한 뒤에 이어 붙이는 방법
- 여러개의 임계값을 이용할 수 있음
◼ rice.png 이미지를 가로와 세로로 각각 4등분하여
전역 이진화와 지역 이진화를 적용하기
# rice.png 영상을 이용하여 가로 4등분, 세로 4등분하고 자동 이진화를 적용해보자.
# 전역(자동) 이진화와 비교
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)
# 지역 이진화
dst2 = np.zeros(img.shape, np.uint8)
bw = img.shape[1] # 4
bh = img.shape[0] # 4
# 4x4 영역으로 나누어 지역 이진화 수행
for x in range(4):
for y 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()
|
4. 적응형 이진화
- 영상을 여러 영역으로 나눈뒤, 그 주변 픽셀 값만 활요하여 임계값을 구함
- 노이즈를 제거한 뒤에 Otsu 이잔화를 적용
◼ 이미지에 적응형 임계값을 적용하기
cv2.adaptiveThreshold (영상, 임계값을 만족하는 픽셀에 적용할 값, 임계값 결정 방법, Threshold 적용 방법, 블록 사이즈, 가감할 상수) cv2.ADAPTIVE_THRESH_MEAN_C: 이웃 픽셀의 평균으로 결정 -> 선명하지만 잡티가 많아짐
cv2.ADAPTIVE_THRESH_GAISSIAN_C: 가우기안 분포에 따른 가중치의 합으로 결정 -> 선명도는 조금 떨어지지만 잡티가 적음 |
|
◼ sudoku.jpg 이미지에 대해 전역 이진화와 적응형 이진화를 비교하여 결과를 시각화
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('./sudoku.jpg', cv2.IMREAD_GRAYSCALE)
# 전역 이진화 (OTSU 알고리즘 사용)
th, dst1 = cv2.threshold(img, 0 ,255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 적응형 이진화 (평균 기반)
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) # 2x2의 subplot 중 i+1 번째에 배치
plt.title(k) # 제목 설정
plt.imshow(v, 'gray') # 이미지 표시 (흑백으로)
plt.show()
|
1. 이미지 유사도- 픽셀 값의 분표가 서로 비슷하다면 유사한 이미지일 확률이 높음
🔵 비교 알고리즘
cv2.compareHist(히스트그램1, 히스트그램2, 알고리즘) cv2.HISTCMP_CORREL: 상관관계(1: 완정 일치, -1: 완전 불일치, 0: 무관계) |
◼ 예제 : 여러 이미지 간의 히스토그램 유사도를 비교하기
import cv2 import matplotlib.pyplot as plt
import numpy as np
img1 = cv2.imread('./taekwonv1.jpg')
img2 = cv2.imread('./taekwonv2.jpg')
img3 = cv2.imread('./taekwonv3.jpg')
img4 = cv2.imread('./dr_ochanomizu.jpg')
imgs = [img1, img2, img3, img4]
hists = []
# 각 이미지에 대해 히스토그램 계산 및 비교를 수행
for i, img in enumerate(imgs):
plt.subplot(1, len(imgs), i+1)
plt.title('img%d' % (i+1))
plt.axis('off')
plt.imshow(img[:, :, ::-1])
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1], None, [180,256], [0, 180, 0, 256])
cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)
hists.append(hist)
query = hists[0]
methods = {'CORREL': cv2.HISTCMP_CORREL, 'CHISQR': cv2.HISTCMP_CHISQR, 'INTERSECT': cv2.HISTCMP_INTERSECT, 'BHATTACHARYYA': cv2.HISTCMP_BHATTACHARYYA}
# 각 메소드별로 히스토그램 유사도를 계산하고 출력
for j, (name, flag) in enumerate(methods.items()):
print('%-10s' % name, end = '\t')
for i, (hist, img) in enumerate(zip(hists, imgs)):
ret = cv2.compareHist(query, hist, flag)
# INTERSECT 메소드의 경우 정규화된 결과를 사용하여 출력
if flag == cv2.HISTCMP_INTERSECT:
ret = ret/np.sum(query)
print('img%d:%7.2f' % (i+1, ret), end = '\t')
print()
plt.show()
|
![]() |
2. 영상의 변환
- 영상을 구상하는 픽셀의 배치 구조를 변경함으로 전체 영상의 모양을 바꾸는 작업
🔵이미지 이동(translate)
M = [1 0 a] [0 1 b] x 방향으로 a 만큼, y 방향으로 b 만큼 이동하는 행렬 cv2.warpaffine(영상, 2 * 3 변환행렬, 결과, 보간법 알고리즘)
|
이미지 이동(translate) : (0,0)을 매게변수로 전달하면 입력 영상과 같은 행렬을 반환 원래 있던 좌표에 이동시키려는 거리 만큼 연산(shift) |
🔵보간법 일고리즘(interpolation)
cv2.INTER_LINEAR: 인접한 4개의 픽셀 값에 거리 가중치 사용 -> 속도는 빠르지만 퀄리티가 좀 떨어짐 cv2.INTRE_NEAREST: 가장 가까운 픽셀 값 사용 -> 속도가 가장 빨르지만 퀄리티가 떨어짐 cv2.INTER_AREA: 픽셀 영역 관계를 이용한 재샘플링 -> 영역적인 정보를 추출해서 결과 영상을 세팅하는 방법, 다운샘플링시 효과적 cv2.INTER_CUBIC: 인접한 16개의 픽셀 값에 사중치를 사용 -> 퀄리티는 가장 촣치만 속도가 떨어짐 |
◼ 예제: 이미지 이동 / cv2.warpaffine() 사용하기
import cv2
import numpy as np
img = cv2.imread('../Day1/dog.bmp')
# [1, 0, a], [0, 1, b]
aff = np.array([[1, 0, 150], [0, 1, 100]], dtype=np.float32)
dst = cv2.warpAffine(img, aff, (0, 0))
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
|
🔵이미지 크기 변환 (resize)
cv2.resize(영상, 결과, x와 y방향 스케일 비율, 보간법)
|
영상의 크기를 원본 영상보다 크게 또는 작게 만드는 변환 |
◼ 예제: 이미지 확대하기 / cv2.INTER_NEAREST, cv2.INTER_CUBIC 사용하기
import cv2
img = cv2.imread('../Day1/dog.bmp')
dst1 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
dst2 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_CUBIC)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1[400:800, 200:600])
cv2.imshow('dst2', dst1[400:800, 200:600])
cv2.waitKey()
|
![]() |
🔵 회전(rotation)
cv2.getRotationMatrix2D(중심 좌표, 회전각도, 확대비율) -> affine 행렬 |
영상의 특정 각도 만큼 회전시키는 변환(반시계 방향) * 회전각도: 반시계방향(기본값), 음수는 시계방향 * 확대비율: 0~1 사이의 실수 |
◼ 예제: 이미지 회전시키기
import cv2
img = cv2.imread('../Day1/dog.bmp')
cp = (img.shape[1] / 2, img.shape[0] / 2)
rot = cv2.getRotationMatrix2D(cp, 30, 0.5)
dst = cv2.warpAffine(img, rot, (0, 0))
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
|
![]() |
🔵투시변환(perspective)
cv2.getPerspectiveTransforms(영상, 4개의 결과 좌표점) -> 투시 변환 행렬 cv2.wrapPerspective(영상, 투시 변환 행렬, 결과 영상 크기) |
- 직사각형 형태의 영상을 임의의 입체감 있는 사각형 형태로 변경할 수 있는 변환 - 원본 영상에 있는 직선은 결과 영상에서 그대로 유지 되지 않고 평행 관계가 깨질 수 있음 - 투시 변환은 보통 3*3 크기의 실수 행렬로 표현 - 8개의 파라미터로 표현할 수 있지만, 좌표 계산 편의상 9개의 원소를 갖는 행렬로 표현 |
◼ 예제 : 이미지 투시변환하기
import cv2
import numpy as np
img = cv2.imread('./pic.jpg')
w, h = 600, 400
srcQuad = np.array([[370, 173], [1220, 155], [1420, 840], [210, 850]], np.float32)
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()
|
◼ 방법1
import cv2
import numpy as np
# 전역 변수 선언
points = []
dragging_point_index = -1
image_path = './namecard.jpg'
# 마우스 이벤트 핸들러 함수
def mouse_handler(event, x, y, flags, param):
global points, image, dragging_point_index
if event == cv2.EVENT_LBUTTONDOWN:
for i, point in enumerate(points):
if abs(point[0] - x) < 10 and abs(point[1] - y) < 10:
dragging_point_index = i
break
elif event == cv2.EVENT_MOUSEMOVE:
if dragging_point_index != -1:
points[dragging_point_index] = (x, y)
redraw_image()
elif event == cv2.EVENT_LBUTTONUP:
dragging_point_index = -1
def redraw_image():
global image
image = orig_image.copy()
for i, point in enumerate(points):
cv2.circle(image, point, 10, (255, 0, 255), -1) # 큰 핑크색 점
if i > 0:
cv2.line(image, points[i-1], point, (255, 0, 255), 2) # 핑크색 선
if len(points) == 4:
cv2.line(image, points[3], points[0], (255, 0, 255), 2) # 마지막 점과 첫 점을 연결하는 선
cv2.imshow("Image", image)
# 이미지를 불러오고 초기화
image = cv2.imread(image_path)
orig_image = image.copy()
# 초기 네 점 설정 (이미지의 모서리 근처에 배치)
h, w, _ = orig_image.shape
points = [(50, 50), (w - 50, 50), (w - 50, h - 50), (50, h - 50)]
redraw_image()
cv2.setMouseCallback("Image", mouse_handler)
print("이미지에서 네 점을 선택하고 움직이세요. Enter 키를 누르면 완료됩니다.")
while True:
key = cv2.waitKey(1) & 0xFF
if key == 13: # Enter 키를 누르면 변환된 이미지를 새 창에 표시
if len(points) == 4:
# 원본 이미지의 크기를 얻음
h, w, _ = orig_image.shape
# 선택한 네 점
pts1 = np.float32(points)
# 대상 좌표 설정 (직사각형으로 변환)
pts2 = np.float32([[0, 0], [w, 0], [w, h], [0, h]])
# 변환 행렬 계산
matrix = cv2.getPerspectiveTransform(pts1, pts2)
# 원근 변환 적용
result = cv2.warpPerspective(orig_image, matrix, (w, h))
# 결과 이미지 표시
cv2.imshow("Transformed Image", result)
print("변환된 이미지가 새 창에 표시됩니다.")
else:
print("네 점이 선택되지 않았습니다.")
elif key == 27: # Esc 키를 누르면 종료
break
cv2.destroyAllWindows()
|
◼ 방법2
import cv2
import numpy as np
import sys
img = cv2.imread('./namecard.jpg')
h, w = img.shape[:2]
dh = 500
# A4용지 크기: 210*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]
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, 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
disp = drawROI(img, srcQuad)
cv2.namedWindow('img')
cv2.setMouseCallback('img', onMouse)
cv2.imshow('img', disp)
while True:
key = cv2.waitKey()
if key == 13:
break
elif key == 27:
sys.exit()
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(img, pers, (dw, dh), flags=cv2.INTER_CUBIC)
cv2.imshow('dst', dst)
cv2.waitKey()
|
![]() ![]() ![]() |
'Python > 컴퓨터 비전' 카테고리의 다른 글
2024-07-23 모폴로지 변환, 레이블링, OCR (0) | 2024.07.29 |
---|---|
2024-08-22 필터링, 블러링 (0) | 2024.07.25 |
2024-07-17 영상 화소처리, 평활화, 색공간, CLAHE, 정규화 (0) | 2024.07.23 |
2024-07-16 OpenCV 라이브러리 (0) | 2024.07.22 |
2024-07-16 컴퓨터 비전 (1) | 2024.07.18 |