1. 필터링
필터링은 커널(필터)이라 불리는 행렬을 정의하여 이미지 위를 이동하면서 커널과 겹치는 부분의 픽셀에 대해 연산을 수행하는 작업을 말합니다. 이 과정에서 연산된 결과값은 기존의 이미지 픽셀을 대체하여 새로운 이미지를 생성합니다.
filter2D(영상, 이미지깊이, 커널, 중심점 좌표, 추가될 값, 가장자리 화소 처리)
- 이미지 깊이: -1 (입력과 동일)
- 커널: 3x3, 5x5 등 크기에 따라 결과가 달라짐
- 중심점 좌표: 커널의 중심점 위치
- 추가될 값: 가장자리 화소 처리 방법에 따라 추가되는 값
- 가장자리 화소 처리:
- BORDER_CONSTANT: 0 또는 검은색으로 처리되는 가장자리 픽셀 (000abcdef000)
- BORDER_REPLICATE: 이미지 가장자리 화소를 복사하여 처리 (aaaabcdeffff)
.... 등등
(1) 블러링(Blurring)
블러링은 영상을 흐릿하게 만드는 작업으로, 초점이 맞지 않은 듯한 효과를 주는데 사용됩니다.
- 평균 블러링
- cv2.blur(영상, 커널)
- 일반적인 블러링 방법으로, 균일한 값을 갖는 정규화된 커널을 사용하여 이미지를 필터링합니다.
- 커널 영역 내의 픽셀들의 평균 값을 사용하여 해당 픽셀을 대체합니다.
- 주변 픽셀들의 평균값을 적용함으로써 전체적으로 흐려지며, 커널 크기가 클수록 더 많이 흐려집니다.
- 가우시안 블러링
- cv2.GaussianBlur(영상, 출력영상, 커널)
- 가우시안 분포를 따르는 커널을 사용하여 블러링하는 방법입니다.
- 가까운 픽셀에 더 큰 가중치를 주고, 먼 픽셀에는 작은 가중치를 주어 노이즈를 제거하면서도 원본 영상과 유사한 결과를 얻을 수 있습니다.
- 미디언 블러링
- cv2.medianBlur(영상, 커널)
- 커널 내의 픽셀 값들의 중앙값을 사용하여 해당 픽셀을 대체합니다.
- 주로 소금-후추 잡음을 제거하는 데 사용됩니다.
- 바이레터럴 필터(Bilateral Filter)
- cv2.bilateralFilter(영상, 픽셀의 거리, 시그마 컬러, 시그마 스페이스)
- 경계를 유지하면서 잡음을 제거하기 위한 필터링 기법입니다.
- 픽셀의 거리, 시그마 컬러, 시그마 스페이스 등의 파라미터를 조절하여 경계를 보존하면서도 잡음을 효과적으로 제거할 수 있습니다.
- 하지만 처리 속도가 다소 느릴 수 있습니다.
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('./dog.bmp')
dst1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst2 = cv2.blur(img, (3,3))
cv2.imshow('img', img)
cv2.imshow('dst2',dst2)
cv2.waitKey()
plt.figure(figsize=(10, 5))
for i, k in enumerate([5, 7, 9]):
kernel = np.ones((k,k)) / k ** 2
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()

import cv2
img = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE)
# 가우시안 블러는 잡티를 없앨 때 많이 씀
dst1 = cv2.GaussianBlur(img, (0,0), 1)
# dst1 = cv2.GaussianBlur(img, (0,0), 3)
dst2 = cv2.blur(img, (3,3))
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

import cv2
img = cv2.imread('./noise.bmp', cv2.IMREAD_GRAYSCALE)
dst = cv2.medianBlur(img, 3)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()

import cv2
img = cv2.imread('./gaussian_noise.jpg', cv2.IMREAD_GRAYSCALE)
dst1 = cv2.GaussianBlur(img, (5,5), 1)
dst2 = cv2.bilateralFilter(img, 5,80, 80)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

(2) 에지(Edge) 검출
에지 검출은 영상에서 화소의 밝기가 급격하게 변하는 부분, 즉 물체의 윤곽선을 찾는 작업입니다. 이를 통해 물체의 윤곽선을 알 수 있습니다. 그 중에서도 '캐니 에지 검출(Canny Edge Detection)'은 상당한 신뢰성을 가진 방법으로 에지를 검출합니다.
cv2.Canny(영상, 최소임계값, 최대임계값, 커널)
- 최소임계값과 최대임계값은 두 개의 경계 값을 지정하여 에지를 찾을 때 사용됩니다.
import cv2
import numpy as np
img = cv2.imread('./dog.bmp')
med_val = np.median(img) # 평균 구해보기
# 범위가 넓을수록 더 많은 에지를 감지
lower = int(max(0, 0.7 * med_val))
upper = int(min(255, 1.3 * med_val))
dst = cv2.GaussianBlur(img, (3,3), 0, 0) # 노이즈를 한번 없애고 적용
dst = cv2.Canny(dst, lower, upper, 3)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()

문제.
웹갬 영상에서 스페이스바를 누를 때마다 필터링을 바꾸는 프로그램을 작성해보자.
(단, 영상은 '일반영상', '가우시안 필터영상', '케니 필터영상' 순으로 변경)
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
def blur_filter(img):
img = cv2.GaussianBlur(img, (0, 0), 3)
return img
def canny_filter(img):
med_val = np.median(img)
lower = int(max(0, 0.4 * med_val))
upper = int(min(255, 1.5 * med_val))
dst = cv2.GaussianBlur(img, (3, 3), 0, 0)
dst = cv2.Canny(dst, lower, upper, 3)
return dst
cam_mode = 0
while True:
ret, frame = cap.read()
if cam_mode == 1:
frame = blur_filter(frame)
elif cam_mode == 2:
frame = canny_filter(frame)
cv2.imshow('frame', frame)
key = cv2.waitKey(10)
if key == 27:
break
elif key == ord(' '):
cam_mode += 1
if cam_mode == 3:
cam_mode = 0
cap.release()
2. 모폴로지 처리
모폴로지 처리는 영상의 밝은 영역이나 어두운 영역을 축소하거나 확대하는 기법을 의미합니다.
cv2.getStructuringElement(구조 요소의 모양, 사이즈)
- 구조 요소의 모양
cv2.MORPH_RECT: 사각형
cv2.MORPH_ELLIPSE: 타원형
cv2.MORPH_CROSS: 십자형
cv2.getStructuringElement() 함수로 구조 요소를 생성하고, 이를 cv2.morphologyEx() 함수에 전달하여 실제로 모폴로지 연산을 수행합니다.
cv2.morphologyEx(영상, 연산방법, 구조요소)
- 연산 방법
- 팽창 연산(cv2.MORPH_DILATE)
- 침식 연산(cv2.MORPH_ERODE)
- 열림 연산(cv2.MORPH_OPEN)
- 닫힘 연산(cv2.MORPH_CLOSE)
- 그레디언트 연산(cv2.MORPH_GRADIENT) 등
침식(Erosion) 연산
cv2.erode(영상, 구조요소, 출력영상, 고정점 위치)
- 이미지를 깎아내는 연산으로, 객체 크기는 줄어들고 배경은 확대됩니다.
- 작은 크기의 객체(잡음)를 제거하는 효과가 있으며, 어두운 부분의 노이즈를 제거하는 데에 효과적입니다.
팽창(Dilation) 연산
cv2.dilate(영상, 구조요소, 출력영상, 고정점 위치)
- 팽창 연산은 객체의 외각을 확대시키며, 객체 크기는 증가하고 배경은 감소합니다.
- 객체 내부의 홀을 채우는 효과가 있으며, 밝은 부분의 노이즈를 제거하는 데에 효과적입니다.
열림(Opening)
- 침식 연산과 팽창 연산을 조합하여 수행됩니다.
- 침식 연산 후에 팽창 연산을 적용함으로써 밝은 영역을 줄이고 어두운 영역을 늘립니다.
- 객체 크기 감소를 원래대로 복구할 수 있습니다.
닫힘(Closing)
- 팽창 연산과 침식 연산을 조합하여 수행됩니다.
- 팽창 연산 후에 침식 연산을 적용함으로써 어두운 영역을 줄이고 밝은 영역을 늘립니다.
- 이후 다시 침식 연산을 적용하여 늘어난 영역을 복구합니다.
그레디언트(Gradient)
- 팽창 연산과 침식 연산의 조합으로, 열림 연산이나 닫힘 연산과는 달리 입력 이미지에 각각 팽창 연산과 침식 연산을 적용한 후, 그 차이를 계산합니다.
import cv2
img = cv2.imread('./circuit.bmp', cv2.IMREAD_GRAYSCALE)
gse = cv2.getStructuringElement(cv2.MORPH_RECT, (5,3))
dst1 = cv2.erode(img, gse) # 없는 부분에 맞춰서 깎아냄
dst2 = cv2.dilate(img, None) # 튀어 나온 부분에 맞춰서 팽창
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

3. 레이블링
레이블링은 이미지 처리에서 객체와 배경 영역을 명확하게 구분하는 과정으로, 이진화와 모폴로지 연산을 통해 수행됩니다. 레이블링을 통해 객체들을 분할하여 위치, 크기, 모양, ROI(Region of Interest) 등의 정보를 얻을 수 있습니다. (서로 연결되어 있는 객체 픽셀에 고유번호를 할당하여 영역 기반 모양분석, 레이블맵, 바운딩 박스, 픽셀 개수, 무게 중심, 좌표 등의 정보를 반환 할 수 있음)
주요 함수로는 cv2.connectedComponents()와 cv2.connectedComponentsWithStats()가 있습니다.
- cv2.connectedComponents(영상, 레이블맵): 객체 갯수와 레이블 맵 행렬 반환
- cv2.connectedComponentsWithStats(영상, 레이블맵): 객체 갯수, 레이블 맵 행렬, 객체 위치, 가로세로 길이, 면적 등의 정보 반환
* 레이블맵 : 픽셀 연결 관계(4방향 연결, 8방향 연결)
import cv2
img = cv2.imread('./keyboard.bmp', cv2.IMREAD_GRAYSCALE)
_, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(img_bin)
print(cnt)
print(labels)
print(stats)
print(centroids)
for i in range(1,cnt):
(x, y, w, h, area) = stats[i]
if area < 20:
continue
cv2.rectangle(dst, (x, y, w, h), (0, 255,255))
cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
- cnt: 객체의 개수를 나타내는 변수입니다. 배경을 제외한 객체의 개수가 반환됩니다.
- labels: 입력 이미지와 동일한 크기의 배열로, 각 픽셀에 할당된 레이블(객체 번호)을 포함합니다.
- stats: 객체에 대한 통계 정보를 담은 배열입니다. 각 행은 하나의 객체에 대한 정보를 포함하며, [x, y, width, height, area] 형식으로 구성되어 있습니다.
- (x, y): 객체의 왼쪽 위 꼭짓점의 좌표
- width, height: 객체의 너비와 높이
- area: 객체의 픽셀 수
- centroids: 객체의 중심 좌표를 포함하는 배열입니다. 각 행은 각 객체의 중심 좌표 (x, y)를 나타냅니다.

4. 객체의 외곽선 검출
cv2.findContours(영상, 검출모드, 외곽선 좌표 근사화 방법)
* 검출모드
RETR_EXTERNAL: 객체 외부 외곽선만 검출
RETR_LIST: 객체 외부, 내부 외곽선 모두 검출
RETR_CCOMP: 모든 외곽선 검출, 2단계 계층 구조를 구성
RETR_ TREE: 모든 외곽선 검출, 전체 계층 구조를 구성
* 외곽선 좌표 근사화 방법
CHAIN_APPROX_NONE: 모든 외곽선 좌표를 저장
CHAIN_APPROX_SIMPLE: 외곽선 중에서 수평, 수직, 대각선 성분의 끝점만 저장
cv2.drawContours(영상, 외곽선 좌표 정보, 외곽선 인덱스, 색상, 두께)
* 외곽선 인덱스: -1을 지정하면 모든 외곽선을 그림
import cv2
import random
import numpy as np
img = cv2.imread('./milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
_, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h,w = img.shape[:2]
dst = np.zeros((h,w,3), np.uint8)
for i in range(len(contours)):
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, i, color, 2)
cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()

- 외곽선의 길이 구하기
cv2.arcLenght(외곽선 좌표, 폐곡선 여부)
- 면적 구하기
cv2.contourArea(외곽선 좌표, False)
- 바운딩 박스 구하기
cv2.boundingRect(외곽선 좌표)
import cv2
import random
img = cv2.imread('./contours.bmp', cv2.IMREAD_GRAYSCALE)
contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# 랜덤으로 컬러 뽑아보기
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
print(color)
# -1을 사용하면 검출된 모든 외곽선을 그림
cv2.drawContours(dst, contours, -1, color, 3)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()

외곽선 근사화
외곽선 근사화는 검출한 외곽선 정보를 분석하여 정점 수가 적은 외곽선 또는 다각형으로 표현할 수 있게 만드는 과정입니다.
- 근사화 정밀도 조절: 입력 외곽선과 근사화 된 외곽선 사이의 최대 거리. 값이 작을수록 다각형이 정확해지고, 꼭지점 수가 늘어남
cv2.approxPolyDP(외곽선 좌표, 근사화 정밀도 조절, 폐곡선 여부)
- contour에 볼록한 부분이 있는지 체크(있으면 True, 없으면 False)
cv2.isContourConvex()
- contour에 있는 볼록한 부분을 제거
cv2.convexHull()
import cv2
img = cv2.imread('./hand.jpg')
cpy = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thr = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
contour, _ = cv2.findContours(thr, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(contour[0])
cnt = contour[0]
cv2.drawContours(img, [cnt], -1, (255, 0, 0), 2)
check = cv2.isContourConvex(cnt)
print(check)
if not check:
hull = cv2.convexHull(cnt)
cv2.drawContours(cpy, [hull], -1, (0, 255, 0), 2)
cv2.imshow('hull', cpy)
cv2.imshow('contour', img)
cv2.waitKey()

'CV(Computer Vision)' 카테고리의 다른 글
OCR (0) | 2024.02.27 |
---|---|
영상의 변환/ 적응형 이진화/ 유사도 (0) | 2024.02.25 |
관심 영역/ 이진화 (0) | 2024.02.18 |
이미지 처리 기법 (0) | 2024.02.16 |
영상처리 기초 : 화소처리 (0) | 2024.02.10 |