딥러닝
[딥러닝] 블러링(Blurring), 엣지(edge) 검출, 모폴로지(morphology), 레이블링(labeling), 외곽선(contours)
Uno_says
2024. 8. 23. 17:17
728x90
1. 블러링(Blurring)
- 초점이 맞지 않은 듯 영상을 흐릿하게 하는 작업
- 평균 블러링: 일반적인 블러링 방법. 균일한 값을 정규화 된 커널을 이용한 이미지 블러링 방법
- 가우시안 블러링: 가우시안 분포를 갖는 커널로 블러링 하는 방법. 대상 픽셀에 가까울수록 많은 영향을 주고, 멀어질수록 적은 영향을 주기 때문에 원래 영상과 비슷하면서도 노이즈를 제거하는 효과가 있다.
- 미디언 블러링: 커널의 픽셀값 중 중앙값을 선택하여 픽셀을 정렬하는 방법. 소금-후추 잡음을 제거하는 효과.
- 바이레터럴 필터 블러링: 기존 블러링의 문제점을 해결하기 위한 방법. 일반적인 블러링은 잡음을 제거하는 효과는 뛰어났지만 해당 잡음의 경계도 흐릿하게 만드는 문제가 발생. 경계도 뚜렷하고 노이즈도 제거되는 효과가 있지만 속도가 느림.
- cv2.bilateralFilter(영상, 픽셀의 거리, 시그마 컬러 범위, 시그마 스페이스 범위)
✔️ 평균 블러링(cv2.blur())
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('./dog.bmp')
dst1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # matplotlib 사용을 위해 BGR -> RGB
dst2 = cv2.blur(img, (7, 7)) # img에 (7, 7)크기의 평균 블러를 적용함
cv2.imshow('img', img)
cv2.imshow('dst2', dst2)
plt.figure(figsize=(10, 5)) # 가로 10인치, 세로 5인치의 그래프 생성
for i, k in enumerate([5, 7, 9]):
kernel = np.ones((k , k)) / k ** 2 # k x k 크기의 평균값 필터 커널을 만듦
filtering = cv2.filter2D(dst1, -1, kernel) # dst1 이미지에 이 커널을 적용하여 필터링함
plt.subplot(1, 3, i+1)
plt.imshow(filtering)
plt.title('kernel size: {}'.format(k))
plt.axis('off')
plt.show()
cv2.waitKey()
✔️ cv2.GaussianBlur()
import cv2
img = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE)
dst1 = cv2.GaussianBlur(img, (0, 0), 2) # (0, 0)은 커널 크기, 자동으로 커널 크기 결정하도록 설정됨
# 2: 표준 편차로 값이 클수록 블러 효과가 강해짐
dst2 = cv2.blur(img, (5, 5)) # 5x5 커널 사용
cv2.imshow('img', img)
cv2.imshow('dst1', dst1) # 가우시안 블러는 이미지의 흐림 정도를 제어하기 위해 사용됨
cv2.imshow('dst2', dst2) # 평균 블러는 단순하게 주변 픽셀들의 평균을 계산하여 흐리게 만듦
cv2.waitKey()
✔️ cv2.medianBlur()
import cv2
img = cv2.imread('./noise.bmp', cv2.IMREAD_GRAYSCALE)
dst = cv2.medianBlur(img, 3) # 3: 커널 크기로, 3x3 영역 내에서 중간값을 찾아 픽셀 값을 대체함
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
✔️cv2.bilateralFilter()
import cv2
img = cv2.imread('./gaussian_noise.jpg', cv2.IMREAD_GRAYSCALE)
dst1 = cv2.GaussianBlur(img, (5, 5), 1) # 1: 커널의 표준편차, 이 필터는 노이즈를 줄이며 부드럽게 만듦
dst2 = cv2.bilateralFilter(img, 5, 80, 80) # 5: 필터링 시 고려할 이웃 픽셀의 거리
# 80: 색 공간에서의 시그마 값(공간 거리의 차이에 따른 가중치)
# 80: 좌표 공간에서의 시그마 값(공간 거리의 차이에 따른 가중치)
# 엣지를 보존하면서 노이즈를 제거하기 때문에 가우시안 블러보다 엣지 보존에 더 효과적
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2) # 가우시안 보다 더 선명함
cv2.waitKey()
2. 필터링 연산
- 커널 또는 필터라고 하는 행렬을 정의하고 이미지 위에서 이동해 가며 커널과 겹쳐진 이미지 영역과 연산을 한 후 그 결과를 픽셀을 대신하여 새로운 이미지로 만드는 연산
- cv2.filter2D(영상, -1, 중심점 좌표, 추가할 값, 가장자리 화소처리)
- -1: 입력과 동일한 크기의 영상을 출력
- 가장자리 화소처리:
- BORDER_CONSTANT: 000a b c d e f g000 (검정색)
- BORDER_REPLICATE: aaa b c d e f ggg (연장)
- ....
3. 에지(edge) 검출
- 물체의 윤곽선(경계선)이 해당
- 영상에서 화소의 밝기가 급격하게 변하는 부분
- 에지를 검출할 수 있으면 물체의 윤곽선을 알 수 있음
- "케니 에지 검출"은 상당한 수준으로 에지를 신뢰성 있게 검출하는 방법
- cv2.Canny(영상, 최소임계값, 최대임계값, 커널)
- 최소입계값, 최대임계값: 두 개의 경계 값(Max, Min)을 지정해서 경계에 있는 영역 픽셀을 찾음
import cv2
import numpy as np
img = cv2.imread('./dog.bmp')
med_val = np.median(img) # 이미지의 모든 픽셀 값의 중간값을 계산 -> 임계값 설정하는 데 사용됨
lower = int(max(0, 0.7 * med_val)) # Canny 엣지 검출을 위한 하한 임계값, 중간값의 70%를 사용, 0보다 작아지지 않도록 보장
upper = int(min(255, 1.3 * med_val)) # 상한 임계값, 중간값의 130%를 사용, 255를 초과하지 않도록 보장
print(lower, upper) # lower보다 높은 값이 엣지로 간주되고, 상한 값보다 높은 값은 확실한 엣지로 간주됨
# Canny 엣지 검출
dst = cv2.Canny(img, lower, upper, 3) # 3: Sobel 커널의 크기(기본값은 3)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
✔️ 웹캠에서 실시간으로 영상을 가져와 블러 필터 및 Canny 엣지 필터 적용하기
import cv2
import numpy as np
# 가우시안 블러
def blur_filter(img):
img = cv2.GaussianBlur(img, (0, 0), 3)
return img
# Canny 엣지 검출 알고리즘
def canny_filter(img):
med_val = np.median(img)
lower = int(max(0, 0.7 * med_val))
upper = int(min(255, 1.3 * med_val))
dst = cv2.Canny(img, lower, upper, 3)
return dst
cap = cv2.VideoCapture(0) # 웹캠을 열어 비디오 스트림을 캡처함
cam_mode = 0
while True: # 실시간으로 웹캠 프레임을 처리함
ret, frame = cap.read() # 프레임 읽어오기
if cam_mode == 1: # 블러 필터를 적용
frame = blur_filter(frame)
elif cam_mode == 2: # Canny 필터를 적용
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() # 웹캠 해제
4. 모폴로지 처리
- 영상의 밝은 영역이나 어두운 영역을 축소 또는 확대하는 기법
- cv2.getStructuringElement(구조 요소의 모양, 사이즈)
- 구조 요소의 모양:
- 직사각형(cv2.MORPH_RECT)
- 단순한 형태로 모든 요소가 같은 값을 가지는 정사각형 또는 직사각형
- 팽창과 침식 연산에서 동일하게 작동
- 객체 가장자리를 다라 명확한 변화를 줄 때 유용
- 타원형(cv2.MORPH_ELLIPSE)
- 가장자리 부분을 더 부드럽게 처리
- 객체의 둥근 모양을 유지하면서 노이즈를 제거할 때 유용
- 십자형(cv2.MORPH_CROSS)
- 중심을 기준으로 수직 및 수평 방향으로 영향
- 얇은 라인 구조를 강화하거나 제거하는 데 유용
- 침식(erosion) 연산
- cv2.erode(영상, 구조요소, 출력영상, 고정점 위치)
- 이미지를 깎아 내는 연산
- 객체의 크기가 감소되고 배경이 확대
- 팽창(dilation) 연산
- cv2.dilate(영상, 구조요소, 출력영상, 고정점 위치)
- 물체의 주변을 확장하는 연산
- 객체의 크기가 증가되고 배경은 감소
- 객체 내부에 홀이 있다면 홀을 채울 수도 있음
import cv2
import numpy as np
img = cv2.imread('./circuit.bmp', cv2.IMREAD_GRAYSCALE)
# gse = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 정사각형 객체 만들기
gse = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 7)) # 3x7크기의 직사각형 구조화 요소, 직사각형 모양의 커널을 생성하는 데 사용
dst1 = cv2.dilate(img, gse) # 팽창 연산: img에서 가장 밝은(흰색) 픽셀이 선택되고 주변 픽셀에 확장됨 -> 밝은 영역 확대
dst2 = cv2.erode(img, gse) # 침식 연산: 가장 어두운(검은색) 픽셀이 선택되고 주변 픽셀로 확장됨 -> 어두운 영역 확대, 노이즈 제거
cv2.imshow('img', img)
cv2.imshow('dst1', dst1) # 가로보다 세로 쪽이 더 팽창
cv2.imshow('dst2', dst2) # 가로보다 세로 쪽이 더 침식
cv2.waitKey()
5. 레이블링(labeling)
- 이진호, 모폴로지를 수행하면 객체와 배경 영역을 구분할 수 있게 됨 -> 객체 단위 분석을 통해 각 객체를 분할하여 특징을 분석하고 객체의 위치, 크기 정보, 모양 분석, ROI 추출 등이 가능함 -> 서로 연결되어 있는 객체 픽셀에 고유번호를 할당하여 영역 기반 모양분석, 레이블맵 바운딩 박스, 픽셀 개수, 무게 중심, 좌표 등을 반환할 수 있게 함이 목표
- cv2.connectedComponent(영상, 레이블맵)
- 레이블맵: 픽셀 연결 관계(4방향 연결, 8방향 연결)
- 반환값: 객체의 갯수, 레이블 맵 행렬
- cv2.connectedComponentWithStats(영상, 레이블맵)
- 레이블맵: 픽셀 연결 관계(4방향 연결, 8방향 연결)
- 반환값: 객체 갯수, 레이블 맵 행렬, (객체위치, 가로세로길이, 면적 등 행렬), 무게중심 정보
✔️ cv2.connectedComponentWithStats
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.connectedComponentWithStats(img_bin)
print(cnt) # 발견된 연결 성분의 수(배경 포함)
print(labels) # 각 픽셀이 어떤 성분에 속하는지 나타내는 레이블 이미지
print(stats) # 각 성분에 대한 통계 정보(위치, 크기, 면적 등)
print(centroids) # 각 성분의 중심 좌표
# 성분별로 경계 상자 그리기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i] # 해당 성분의 위치(x, y), 너비(w), 높이(h), 면적(area)를 가져옴
if area < 30: # 성분의 면적이 30보다 작으면 continue
continue
cv2.rectangle(dst, (x, y, w, h), (0, 255, 255)) # 30보다 작지 않으면 해당 성분 주위에 노란색 경계상자 그리기
cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
- 외곽선 검출
- cv2.findContours(영상, 검출모드, 외곽선 좌표 근사화 방법)
- 검출모드:
- cv2.RETR_EXTERNAL: 외부 외곽선만 검출
- cv2.RETR_LIST: 모든 외곽선을 검출, 계층관계는 무시
- cv2.RETR_CCOMP: 모든 외곽선을 검출, 계층관계를 2단계로 구성
- 첫번째 계층: 바깥쪽 외곽선
- 두번째 계층: 내부 윤곽선
- cv2.RETR_TREE: 모든 외곽선을 검출, 전체 계층 관계를 구성
- 근사화 방법: 외곽선 점들의 저장 방식과 정확도를 정의
- cv2.CHAIN_APPROX_NONE: 모든 외곽선의 점을 저장
- cv2.CHAIN_APPROX_SIMPLE: 수평, 수직, 대각선 방향 점들의 끝점만 저장하여 압축
- cv2.findContours(영상, 검출모드, 외곽선 좌표 근사화 방법)
- 외곽선 그리기
- cv2.drawContours(영상, 외곽선 좌표 정보, 외곽선 인덱스, 색상, 두께)
- 외곽선 인덱스: -1(모든 외곽선을 그림)
- cv2.drawContours(영상, 외곽선 좌표 정보, 외곽선 인덱스, 색상, 두께)
- 외곽선 근사화
- 검출한 외곽선 정보를 분석하여 정점수가 적은 외곽선 또는 다각형으로 표현할 수 있게 만드는 것
- cv2.approxPolyDP(외곽선좌표, 근사화 정밀도 조절, 폐곡선 여부)
- 근사화 정밀도 조절: 입력 외곽선과 근사화된 외곽선 사이의 최대 길이. 값이 작을수록 다각형이 정확해지고, 꼭짓점 수가 늘어남
- 외곽선의 길이 구하기
- cv2.arcLength(외곽선 좌표, 폐곡선 여부)
✔️ contours1
import cv2
import random
img = cv2.imread('./contours.bmp', cv2.IMREAD_GRAYSCALE)
# cv2.RETR_CCOMP: 모든 컨투어를 찾고, 계층 구조를 두 개의 레벨로 구분하는 옵션
# cv2.CHAIN_APPROX_NONE: 컨투어의 모든 점을 지정하도록 지정하는 옵션
contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # 컬러 이미지로 변환
# 임의의 색상 선택, 0에서 255까지의 정수
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
# 이미지 위에 검출된 컨투어 그리기
# dst: 그릴 대상 이미지, contours: 그릴 컨투어 리스트, -1: 모든 컨투어를 그리겠다는 의미
cv2.drawContours(dst, contours, -1, color, 3)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
✔️ contours2
import cv2
import math
# 특정 다각형에 대해 레이블을 이미지에 표시
def setLabel(img, pts, label):
(x, y, w, h) = cv2.boundingRect(pts) # 외곽선을 포함하는 가장 작은 직사각형 계산
pt1 = (x, y)
pt2 = (x + w, y + h)
cv2.rectangle(img, pt1, pt2, (0, 0, 255), 2)
cv2.putText(img, label, pt1, cv2.FONT_HERSHEY_DUPLEX, 1, (0, 0, 255))
img = cv2.imread('./polygon.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 이진화, cv2.THRESH_BINARY_INV: 이진화된 이미지를 반전시킴
_, img_bin = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
# 외곽선 검출, cv2.RETR_EXTERNAL: 가장 바깥쪽 외곽선만 찾음
contours, _ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 외곽선 반복 처리 및 도형 분류
for pts in contours:
if cv2.contourArea(pts) < 200: # 외곽선의 면적이 200 미만이면 continue
continue
approx = cv2.approxPolyDP(pts, cv2.arcLength(pts, True) * 0.02, True) # 외곽선의 윤곽을 단순화하여 점의 수를 줄임
vtc = len(approx) # 다각형의 꼭짓점 수
print(vtc)
if vtc == 3: # 삼각형으로 간주
setLabel(img, pts, "TRI")
elif vtc == 4: # 사각형으로 간주
setLabel(img, pts, 'RECT')
else: # 꼭짓점이 4개 이상일 경우
length = cv2.arcLength(pts, True) # 도형의 둘레
area = cv2.contourArea(pts) # 도형의 면적
ratio = 4. * math.pi * area / (length * length) # 원형 비율
if ratio > 0.70:
setLabel(img, pts, 'CIR') # 원으로 간주
else:
setLabel(img, pts, 'NONAME')
cv2.imshow('img', img)
cv2.imshow('gray', gray)
cv2.imshow('img_bin', img_bin)
cv2.waitKey()
💡문제
- 'milkdrop.bmp'를 grayscale로 읽어오기
- 자동이진화(오츠의 이진화 알고리즘) 실행
- 외곽선 검출하고 임의의 색상으로 그리기
- 원본 이미지, 이진화된 이미지, 외곽선이 그려진 이미지 출력하기
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)
# 외곽선 찾기, cv2.RETR_CCOMP은 외곽선의 상하 구조 정보를 함께 제공
contours, _ = cv2.findContours(img_bin, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
h, w = img.shape[:2] # 이미지 크기 가져오기(세로, 가로)
dst = np.zeros((h, w, 3), np.uint8) # (h, w, 3)크기의 검정색(zeros) 이미지를 생성
# 각 외곽선을 임의의 색상으로 그리기
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()
728x90