딥러닝

[딥러닝] 블러링(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()

kernel 크기가 클수록 blur처리되는 부분이 커짐

 

 

✔️ 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.drawContours(영상, 외곽선 좌표 정보, 외곽선 인덱스, 색상, 두께)
      • 외곽선 인덱스: -1(모든 외곽선을 그림)
  • 외곽선 근사화
    • 검출한 외곽선 정보를 분석하여 정점수가 적은 외곽선 또는 다각형으로 표현할 수 있게 만드는 것
    • 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