본문 바로가기

8. OpenCV | CV

7/28(금) IT K-DT(100일차) / 21.필터링(filtering)~26.외곽선검출

 

 

21. 필터링 (filtering)

커널(filter)이라고 하는 행렬을 정의하고
이 커널을 이미지 위에서 이동시켜 가면서 커널과 겹쳐진 이미지 영역과 연산을 한 후
그 결과값을 연산을 진행한 이미지 픽셀을 대신하여 새로운 이미지를 만드는 연산 (CNN의 Mask와 유사한 개념)
    filter2D(영상, 이미지의깊이(-1), 커널, 중심점좌표, 추가될값, 가장자리화소처리)
    이미지깊이: -1(입력과동일)
    커널행렬: 3*3, 5*5 ...와 같이 가로세로가 동일한 정방행렬을 사용.
    중심점좌표: 기본값은 왼쪽상단
    가장자리화소처리
        BORDER_CONSTANT: 픽셀의 끄트머리를 0으로 채우는 처리. (0000abcdefg0000)
        BORDER_REPLICATE: 픽셀의 맨 왼쪽과 맨 오른쪽을 동일한 값으로 채우는 처리. (aaaabcdefgggg)

 

22. 블러링 (blurring)

초점이 맞지 않듯 영상을 흐릿하게 하는 작업

 

22-1. 평균 블러링

    가장 일반적인 블러링 방법으로, 균일한 값을 정규화된 커널을 이용한 이미지 필터링 방법
    커널 영역 내에서 평균값으로 해당 픽셀을 대체함
    주변 픽셀들의 평균값을 적용하면 픽셀 간 차이가 적어져 선명도가 떨어져 전체적으로 흐려짐
    필터의 크기가 클수록 평균 블러링을 적용했을 때 선명도가 떨어짐
    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)
dst2 = cv2.blur(img, (3,3)) # blur()함수를 이용해서 코드 작성
cv2.imshow('img', img)
cv2.imshow('dst2', dst2)

plt.figure(figsize=(10,5))

for i,k in enumerate([3,5,9]):
    kernel = np.ones((k, k)) / k ** 2 # (1/3, 1/5, 1/9)
    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()

cv2.waitKey()

 

 

22-2. 가우시안 블러링

    가우시안 분포를 갖는 커널로 블러링을 하는 것
    (정규분포, 평균 근처에 몰려있는 값들의 개수가 많고 평균에서 멀어질수록 그 개수가 적어지는 분포)
    대상 픽셀에 가까울수록 많은 영향을 주고, 멀어질수록 적은 영향을 주기 때문에 원래의 영상과 비슷하면서도 노이즈를 제거하는 효과가 있음
        cv2.GaussianBlur(영상, 출력영상, 커널)
        출력영상: (0,0)이면 입력 영상과 같음
        커널: 예) 3, 3*3

 

import cv2

img = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE) # 원본
dst1 = cv2.GaussianBlur(img, (0,0), 3) # 가우시안블러를 이용 # 정규분포로 퍼지므로 더욱 뿌옇게 변함
dst2 = cv2.blur(img, (5,5)) #블러를 이용

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

 

 

22-3. 미디언 블러링

    커널의 픽셀값 중 중앙값을 선택
    소금-후추 잡음을 제거하는 효과가 있음.
        cv2.medianBlur(영상, 커널)

    바이레터럴 필터(Bilateral Filter)
    기존 블러링의 문제점(잡음을 제거하는 효과는 뛰어났지만, 경계도 흐릿하게 만드는 문제)을 개선하기 위해 나온 필터링 기법
    경계도 뚜렷하고 노이즈도 제거되는 효과가 있지만 속도가 느리다는 단점이 존재함
        cv2.bilateralFilter(영상, 픽셀의거리, 시그마컬러, 시그마스페이스)
        픽셀의거리(필터의직경): -1(자동결정)
        sigmaColor: 색공간의 시그마 값 // 시그마 값: 표준편차를 조절해주는 역할
        sigmaSpace: 좌표공간의 시그마 값(값이 크면 멀리 떨어져 있는 픽셀들이 서로 영향을 미침)

 

import cv2

img = cv2.imread('./noise.bmp', cv2.IMREAD_GRAYSCALE) # 소금-후추 잡음이 있는 원본
dst = cv2.medianBlur(img, 3) # 커널 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), 0) # 가우시안블러링을 사용
dst2 = cv2.bilateralFilter(img, 5, 80, 80) # 바이레터럴 필터를 사용

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

 

 

23. 엣지(edge) 검출

    엣지(edge):
    영상에서 화소의 밝기가 급격하게 변하는 부분
    물체의 윤곽선(경계선)이 해당됨. 엣지를 검출할 수 있으면 물체의 윤곽선을 알 수 잇음
    케니 엣지 검출: 상당한 수준으로 엣지를 신뢰성있게 검출하는 방법
        1) 노이즈 제거: 5*5 가우시안 블러링 필터를 사용
        2) 경계 그레디언트 방향 계산: 소벨 필터로 경계 및 그레디언트 방향을 검출
        3) 비최대치 억제: 그레디언트 방향에서 검출된 경계 중 가장 큰 값만 선택하고 나머지는 제거함
        4) 이력 스레시홀딩: 두 개의 경계값(Max, Min)을 지정해서 경계 영역에 있는 픽셀들 중 큰 경계값(Max) 밖의 픽셀과 연결성이 없는 픽셀을 제거
        cv2.Canny(영상, 최소임계값, 최대임계값, 커널)

import cv2
import numpy as np

img = cv2.imread('./dog.bmp')

cv2.imshow('img', img)
med_val = np.median(img)
lower = int(max(0, 0.7*med_val)) # 0~median value의 70%의 범위에서 lower값
upper = int(min(255, 1.3*med_val)) # 255~median value의 130%의 범위에서 upper값

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('./woman.mp4')

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.7 * med_val))
    upper = int(max(255, 1.3 * med_val))

    img = cv2.GaussianBlur(img, (3,3), 0, 0)
    img = cv2.Canny(img, lower, upper, 3)
    return img

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()

 

 

24. 모폴로지 (morphology) 처리

영상의 밝은 영역이나 어두운 영역을 축소, 확대하는 기법
모폴로지 구조 요소를 생성
    cv2.getStructuringElement(구조요소의모양, 사이즈)
    구조요소의 모양
        cv2.MORPH_RECT: 사각형
        cv2.MORPH_ELLIPSE: 타원형
        cv2.MORPH_CROSS: 십자가

 

24-1. 침식(erosion) 연산

    이미지를 깎아내는 연산
    객체 크기는 감소하고 배경은 확대함
    작은 크기의 객체(잡음)제거 효과가 있음
        cv2.erode(영상, 구조요소, 출력영상, 고정점위치)

 

24-2. 팽창(dilation) 연산

    물체의 주변을 확장하는 연산
    팽창 연산은 객체 외곽을 확대시키는 연산
    객체 크기는 증가하고 배경은 감소하는 효과가 있음
    객체 내부의 홀이 있다면 홀이 채워지는 효과
        cv2.dilate(영상, 구조요소, 출력영상, 고정점위치)
침식은 어두운 부분의 노이즈를 제거하는 효과
팽창은 밝은 부분의 노이즈를 제거하는 효과
노이즈 제거 효과는 좋으나, 원래 모양이 훌쭉해지거나 뚱뚱해지는 변형이 일어남

 

import cv2

img = cv2.imread('./circuit.bmp', cv2.IMREAD_GRAYSCALE)
se = cv2.getStructuringElement(cv2.MORPH_RECT, (5,3))
dst1 = cv2.erode(img, se)
dst2 = cv2.dilate(img, None) # 기본값은 3*3


cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

 

 

24-3. 열림(opening)

    팽창연산과 침식연산의 조합
    침식연산을 적용한 다음, 팽창연산을 적용
    침식연산으로 인해 밝은 영역이 줄어들고, 어두운 영역이 늘어남
    객체의 크기 감소를 원래대로 복구할 수 있음

 

24-4. 닫힘(closing)

    팽창연산과 침식연산의 조합
    팽창연산을 적용한 다음, 침식연산을 적용
    팽창연산으로 인해 어두운 영역이 줄어들고, 밝은 영역이 늘어남
    침식연산을 적용하면 밝은 영역이 줄어들고, 어두운 영역이 늘어남
    객체 내부의 홀이 사라지면서 발생한 크기 증가를 복구할 수 있음

 

24-5. 그레디언트(gradient)

    팽창연산과 침식연산의 조합
    열림,닫힘 연산과 달리 입력 이미지에 각각 팽창연산과 침식연산을 적용하고 감산을 진행
    cv2.morphologyEx(영상, 연산방법, 구조요소)
    연산방법
        cv2.MORPH_DILATE: 팽창연산
        cv2.MORPH_ERODE: 침식연산
        cv2.MORPH_OPEN: 열린연산
        cv2.MORPH_CLOSE: 닫힘연산
        cv2.MORPH_GRADIENT: 그레디언트연산

 

25. 레이블링

이진화, 모폴로지를 수행하면 객체와 배경 영역을 구분할 수 있게 됨.
객체 단위 분석을 통해 각 객체를 분할하여 특징을 분석하고 객체의 위치, 크기 정보, 모양 분석, ROI추출 등이 가능해짐
서로 연결되어있는 객체 픽셀에 고유번호를 할당하여 영역기반 모양분석, 레이블맵, 바운딩 박스, 픽셀 개수, 무게중심, 좌표 등을 반환할 수 있게 함
    cv2.connectedComponents(영상, 레이블맵)
    레이블맵: 픽셀연결관계(4방향연결, 8방향연결)
    return되는 것: 객체 갯수, 레이블맵 행렬
    cv2.connectedComponentsWithStats(영상, 레이블맵)
    return되는 것: 객체 갯수, 레이블맵 행렬, (객체위치, 가로세로길이, 면적등행렬), 무게중심정보

 

# keybord에서 문자열을 객체로 잡아서 노란색 네모상자를 삽입해보기

import cv2

img = cv2.imread('./keyboard.bmp', cv2.IMREAD_GRAYSCALE)

_, img_bin = cv2.threshold(img, 0, 255, 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()

 

 

26. 외곽선 검출

레이블링과 함께 영상에서 객체의 정보를 검출하는 방법 중 하나
이진화된 영상에서 검출되며 배경 영역과 닿아 있는 픽셀을 찾아 외곽선으로 인식
외곽선은 객체 외부뿐만이 아니라 객체 내부에도 생길 수 있음
    cv2.findContours(영상, 검출모드, 외곽선 좌표 근사화 방법)
    검출모드
        RETR_EXTERNAL: 객체 외부 외곽선만 검출
        RETR_LIST: 객체 외부, 내부 외곽선 모두 검출
        RETR_CCOMP: 모든 외곽선 검출, 2단계 계층 구조를 구성
        RETR_TREE: 모든 외곽선 검출, 전체 계층 구조를 구성
    외곽선 좌표 근사화 방법
        CHAIN_APPROX_NONE: 모든 외곽선 좌표를 저장
        CHAIN_APPROX_SIMPLE: 외곽선 중에서 수평, 수직, 대각선 성분은 끝 점만 저장
   외곽선 그리기
       cv2.drawContours(영상, 외곽선좌표정보, 외곽선인덱스, 색상, 두께)
       외곽선인덱스: -1(모든 외곽선을 그림)
   외곽선 길이 구하기
        cv2.arclength(외곽선좌표, 폐곡선여부)
   면적 구하기
        cv2.contourArea(외곽선좌표, False)
   바운딩 박스 구하기
        cv2.boundingRect(외곽선좌표)
   외곽선 근사화
   검출한 윤곽선 정보를 분석하여 정점수가 적은 윤곽선 또는 다각형으로 표현할 수 있게 만드는 것
        cv2.approxPolyDP(외곽선좌표, 근사화정밀도조절 ,폐곡선여부)
        근사화정밀도조절: 입력 컨투어와 근사화된 컨투어 사이의 최대 거리.
            값이 작을수록 다각형이 정확해지고 꼭지점의 수가 늘어남.
        cv2.iscontourConvex()
        contour에 볼록이나 오목한 부분이 있는지의 여부를 체크(있으면 True, 없으면 False)
        cv2.convexHull()
        contour에 볼록이나 오목한 부분을 제거

 

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영상으로 변경

color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 랜덤한 색상
print(color)
cv2.drawContours(dst, contours, -1, color, 3)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()

 

 

# 영상의 외곽선을 따보기
import random
import cv2
import numpy as np


img = cv2.imread('./milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
_, img_bin = cv2.threshold(img, 0, 255, 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 = (0, 255, 255)
    cv2.drawContours(dst, contours, i, color, 2)

cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()

 

 

# polygon 이미지의 여러 도형들의 위에 n각혐임을 나타내는 라벨작업

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,255,255), 1)
    cv2.putText(img, label, pt1, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255)) # 글씨를 작성

img = cv2.imread('./polygon.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
_, img_bin = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) # 반전을 시키지 않는다면 외곽선이 전체 1개만 검출이 되어버림.
contours, _ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for pts in contours:
    if cv2.contourArea(pts) < 200:
        continue
    approx = cv2.approxPolyDP(pts, cv2.arcLength(pts, True)*0.02, True)
    print(approx)
    vtc = len(approx)
    print(vtc)
    if vtc == 3: # 꼭지점이 3개인 경우,
        setLabel(img, pts, 'TRI')
    elif vtc == 4:
        setLabel(img, pts, 'RECT')
    else:
         length = cv2.arcLength(pts, True) # 원을 구하는 공식
         area = cv2.contourArea(pts)
         ratio = 4. * math.pi * area / (length * length) # 원

         if ratio > 0.8: # 비율의 값이 0.8이상이면 타원도 원으로 인식함
            setLabel(img, pts, 'CIR')
         else:
            setLabel(img, pts, 'NONAME')

cv2.imshow('img', img)
cv2.waitKey()

 

 

import cv2

img = cv2.imread('./hand.jpg')
cpy = img.copy()

gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) # 회색으로 변경
_, thr = cv2.threshold(gray, 27, 255, cv2.THRESH_BINARY) # 이진화
contour, _ = cv2.findContours(thr, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 외곽선검출
print(contour[0]) # 1번외곽선 검출
cnt = contour[0]

cv2.drawContours(img, [cnt], -1, (255, 0, 0), 2)
check = cv2.isContourConvex(cnt) # 볼록오목한 부분의 존재 여부를 확인
print(check)