본문 바로가기
CV(Computer Vision)

필터링/컨투어링

by 코낄2 2024. 2. 26.

1. 필터링

필터링은 커널(필터)이라 불리는 행렬을 정의하여 이미지 위를 이동하면서 커널과 겹치는 부분의 픽셀에 대해 연산을 수행하는 작업을 말합니다. 이 과정에서 연산된 결과값은 기존의 이미지 픽셀을 대체하여 새로운 이미지를 생성합니다.

filter2D(영상, 이미지깊이, 커널, 중심점 좌표, 추가될 값, 가장자리 화소 처리)

- 이미지 깊이: -1 (입력과 동일)
- 커널: 3x3, 5x5 등 크기에 따라 결과가 달라짐
- 중심점 좌표: 커널의 중심점 위치
- 추가될 값: 가장자리 화소 처리 방법에 따라 추가되는 값
- 가장자리 화소 처리:
    - BORDER_CONSTANT: 0 또는 검은색으로 처리되는 가장자리 픽셀 (000abcdef000)
    - BORDER_REPLICATE: 이미지 가장자리 화소를 복사하여 처리 (aaaabcdeffff)

      .... 등등

 

(1) 블러링(Blurring)

블러링은 영상을 흐릿하게 만드는 작업으로, 초점이 맞지 않은 듯한 효과를 주는데 사용됩니다.

  1. 평균 블러링 
    • cv2.blur(영상, 커널) 
    • 일반적인 블러링 방법으로, 균일한 값을 갖는 정규화된 커널을 사용하여 이미지를 필터링합니다.
    • 커널 영역 내의 픽셀들의 평균 값을 사용하여 해당 픽셀을 대체합니다.
    • 주변 픽셀들의 평균값을 적용함으로써 전체적으로 흐려지며, 커널 크기가 클수록 더 많이 흐려집니다.
    •  
  2. 가우시안 블러링
    • cv2.GaussianBlur(영상, 출력영상, 커널)
    • 가우시안 분포를 따르는 커널을 사용하여 블러링하는 방법입니다.
    • 가까운 픽셀에 더 큰 가중치를 주고, 먼 픽셀에는 작은 가중치를 주어 노이즈를 제거하면서도 원본 영상과 유사한 결과를 얻을 수 있습니다.
  3. 미디언 블러링
    • cv2.medianBlur(영상, 커널)
    • 커널 내의 픽셀 값들의 중앙값을 사용하여 해당 픽셀을 대체합니다.
    • 주로 소금-후추 잡음을 제거하는 데 사용됩니다.
  4. 바이레터럴 필터(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