본문 바로가기
OpenCV/OpenCV-Chapter

CH04 Opencv-Python 필터링(filtering) - blurring, Sharpening

by kiimy 2021. 12. 18.
728x90
728x90

영상의 필터링(image filtering)

  • 영상에서 필요한 정보만 통과시키고 원치 않는 정보는 걸러내는 작업

1. 주파수 공간에서의 필터링(수학적 이론이 필요함)

*저주파 = 부드러운 성분

*고주파 = 픽셀 값이 급격하게 바뀌는 성분

2. 공간의 필터링(Correlation, Deep Learning ==> Conv)

*영상의 픽셀 값을 직접 이용하는 필터링 방법

*대상 좌표의 픽셀 값과 주변 픽셀 값을 동시에 사용

= 주로 마스크(mask) 연산을 이용함 (마스크 = 커널(kernel) = 윈도우(window) = 템플릿(template))


다양한 모양과 크기의 마스크

Anchor = Filter의 가운데(고정점)

마스크의 형태와 값에 따라 필터의 역할이 결정됨

• 영상 부드럽게 만들기(Mean filter, GaussianBlur)

• 영상 날카롭게 만들기(Unsharp mask)

• 에지(edge) 검출(Gradient, Sobel, Canny)

• 잡음 제거(GaussianBlur, Median filter, Bilateral filter(양방향))

 

Filtering 연산 방법

- Mask 연산, 옆으로 이동하면서 각각 연산(Scanning)

(1, 1) 좌표에서 필터링 / (2, 1) 좌표에서 필터링

최외곽 픽셀 처리

- OpenCV는 가상의 픽셀이 있다고 생각하고 연산(= padding)

= 테두리 외삽법(borderType)


영상 부드럽게 만들기

Mean filter

- 픽셀들 간의 그레이스케일 값 변화가 줄어들어 날카로운 에지가 무뎌지고, 영상에 있는 잡음(Noise)의

  영향이 사라지는 효과

- 마스크 크기가 커질수록 평균 값 필터 결과가 더욱 부드러워짐

= 더 많은 연산량이 필요!

cv2.filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, 
			borderType=None) -> dst

'''
ddepth: -1을 지정하면 src와 같은 타입의 dst 영상을 생성
	출력 영상 데이터 타입. (e.g) cv2.CV_8U, cv2.CV_32F, cv2.CV_64F
        == 1byte, 4byte, 8byte
anchor: 고정점 위치. (-1, -1)이면 필터 중앙을 고정점으로 사용
delta: 추가적으로 더할 값
'''
import cv2
import sys
import numpy as np

src = cv2.imread('ch04\\images\\rose.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()


'''
kernel = np.array([[1/9,1/9,1/9],
                   [1/9,1/9,1/9],
                   [1/9,1/9,1/9]], dtype=np.float32) # 기본적으로 float64로 만들어짐

*mean*
kernel = np.ones((3, 3), dtype=np.float64) / 9.
'''
# dst = cv2.filter2D(src, -1, kernel, borderType) 
# ddepth = -1: 입력영상과 동일한 datatype로 생성(grayscale)
# borderType = BORDER_REFLECT_101 or BORDER_DEFAULT


# cv2.blur()
dst = cv2.blur(src, (3,3))

for ksize in (3, 5, 7):
    dst_ksize = cv2.blur(src, (ksize,  ksize))

    text = f'Mean:{ksize}x{ksize}'
    cv2.putText(dst_ksize, text, (10,30), cv2.FONT_HERSHEY_SIMPLEX,\
                fontScale= 1.0, color= 255, thickness= 1, lineType=cv2.LINE_AA)

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

# cv2.imshow('image', src)
# cv2.imshow('dst', dst)
cv2.waitKey()

GaussianBlur

평균값 필터에 의한 블러링의 단점

• 필터링 대상 위치에서 가까이 있는 픽셀과 멀리 있는 픽셀이 모두 같은 가중치를 사용하여 평균을 계산

• 멀리 있는 픽셀의 영향을 많이 받을 수 있음

사용자가 지정한 파라미터로 Bell Shape의 filter를 만들어서 모든 pixel 영역에
대해서 동일한 형태로 Correlation진행

Mean filter / Gaussian filter(Symmetric 좌우대칭)
보통 +-3sigma이면 99.7%데이터를 포함

 

Filter Mask size = (8sigma + 1) or (6sigma + 1)

                      = +-4sigma(float) or +-3sigma(uint8) 

                      = sigma 값이 클수록 더 많은 data를 포함(= 연산속도 느려짐)

ksize를 지정한다는건 
ex) (5,5)를 설정하면 총 (8sigma + 1) or (6sigma + 1) ==> +-41 or +-31
좌우 끝 특정 영역은 사용하지 않겠다는 말(= 평균에 가까운 것만 사용하겠다)
cv2.GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, 
				borderType=None) -> dst
'''
ksize: (0, 0)을 지정하면 sigma 값에 의해 자동 결정됨(왠만하면 (0,0) 쓸것)
ksize = (8시그마 +1) or (6시그마 + 1)
sigmaX: x방향 sigma.
sigmaY: y방향 sigma. 0이면 sigmaX와 같게 설정
'''
# cv2.GaussianBlur(src, ksize, sigmaX)
# sigmax= 1, ksize가 자동으로 설정됨= 값이 커지면 더 blurring 됨
dst = cv2.GaussianBlur(src, (0,0), sigmaX= 3)

# blur( mean filter)의 경우 line(edge?)이 부드럽게 blur처리가 되지 않아
dst2 = cv2.blur(src, (7,7))

for sigma in range(1,6):
    dst_sigma= cv2.GaussianBlur(src, (0,0), sigma)
    text = f'sigma = {sigma}'
    cv2.putText(dst_sigma, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
                1.0, 255, 1, cv2.LINE_AA)

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

영상 날카롭게 만들기

언샤프 마스크(Unsharp mask)

- 날카롭지 않은(unsharp) 영상, 즉, 부드러워진 영상을 이용하여 날카로운 영상을 생성

f(X) - 어둡다가 밝아지는 분포를 가진 그래프

f'(X) = Blur(부드러운 영상)
g(X) = f(X) - f'(X) ==> 날카로운 성분만 남음
h(X) = 2f(X) - f'(X) ==>원래 이미지에서 날카로운 부분이 강조됨

높이가 달라짐 == Contrast가 증가

즉, Edge 금방에서 Contrast를 높여주면 사람 눈에는 Sharp하게 보인다

# sharpening edge 강조 = Contrast(Histogram)
# g(x) = 2f(x) - f1(x)
src = cv2.imread('ch04\\images\\rose.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

blr = cv2.GaussianBlur(src, (0, 0), 2)

# sbutract = 그래프에서 양수 부분만 보임(흰색부분만)
# 그래프 음수 부분도 보이게 할려면 addweigthed
subtract = cv2.subtract(src, blr)

# gamma = 결과를 눈에 잘 보이게 하기 위함 값
# 가중치 부여
# g(x) = 2f(x) - f1(x)
addweighted = cv2.addWeighted(src, 2, blr, -1, 1)

# *np.clip의 내부 연산은 float로 해줘야함(= 2.0)*
# np.clip = float64 / 변환필요
dst = np.clip(2.0*src - blr, 0, 255).astype(np.uint8)

###################################################################

# color 영상

src_ycrcb = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)

# 밝기정보만 가우시안블러
src_f = src_ycrcb[:,:,0].astype(np.float32)

## *np.clip의 내부 연산은 float로 해줘야함(2.0)*
## np.clip = float32 / 변환필요
'''
float32 why?
중간 연산의 과정을 float로 해줘야하는 이유
GaussianBlur의 출력영상 type은 입력영상의 tpye과 같게 설정됨
if uint8의 경우 GaussianBlur의해서 나타나는 출력영상은 소수점 아래가 다짤림
== 정수형태로 변환되기 때문에 미세한 변화가 사라져
'''
blr = cv2.GaussianBlur(src_f, (0,0), 2.0) # 흰색??

# 최종적으로 화면에 보여지기 위한 영상을 만들때 다시 uint8로 변환
src_ycrcb[:,:,0] = np.clip(2.0*src_f - blr, 0, 255).astype(np.uint8)
dst = cv2.cvtColor(src_ycrcb, cv2.COLOR_YCrCb2BGR)

 

728x90

댓글