Python_opencv_02(기본 영상 처리)
히스토그램
- 영상을 구성하는 픽셀들의 빈도수를 측정하여 그래프(히스토그램)으로 표현
- cv2.calcHist(images, channels, mask, histSize, ranges, hist=None, accumulate=None)
- images: 히스토그램을 구할 입력영상 리스트
- channels: 히스토그램을 구할 채널 리스트
- mask: 마스크 영상. 영상전체일 경우 None지정
- histSize: 히스토그램 각 채널의 bin의 개수 리스트. 여기서는 각각의 픽셀값을 셀 것이므로 255개로 지정
- ranges: 히스토그램을 이용해 빈도수를 셀 최솟값과 최대값을 리스트로 설정. [0, 256] 0 ~ 255 (마지막은 포함 안함)
Grayscale 영상의 히스토그램 구하기
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('images/window.jpg', cv2.IMREAD_GRAYSCALE)
plt.imshow(img, cmap='gray')
hist = cv2.calcHist([img] # pixcel값의 분포를 계산할 이미지 (ndarray)
,[0] # pixcel값의 분포를 계산할 채널
,None # pixcel값의 분포를 계산할 영역. 전체영역일 경우 None으로 지정
,[256] # bins : 몇 계급으로 나눌지 결정
,[0, 256] # 빈도수를 셀 pixcel값의 범위
)
type(hist), hist.shape # index : pixcel값, value : 개수
히스토그램 구하기
import numpy as np
plt.bar(np.arange(hist.size), hist.flatten())
plt.xlabel('픽셀값')
plt.ylabel('개수')
plt.show()
컬러영상의 히스토그램 구하기 -> channel 별로 그린다.
이미지 구해오기
lenna = cv2.imread('images/Lenna.png')
print(lenna.shape)
blue = lenna[:, :, 0]
green = lenna[:, :, 1]
red = lenna [:, :, 2]
print(blue.shape, green.shape, red.shape)
split을 이용하여 채널별로 나눌 수 있다.
bgr = cv2.split(lenna) # 채널별로 나눠진 배열들을 튜플로 묶어서 반환
print(type(bgr), len(bgr))
color_labels = ['blue', 'green', 'red']
for channel, color in zip(bgr, color_labels):
hist = cv2.calcHist([channel], [0], None, [256], [0, 256])
plt.plot(np.arange(hist.size), hist.flatten(), color = color)
plt.xlabel('픽셀값')
plt.ylabel('개수')
plt.show()
밝기 조절하기(brightness)
- 영상을 전체적으로 밝게 또는 어둡게 만드는 연산
- 밝기 조절 수식
- 원본좌표(x,y) + n
- n이 양수면 각 좌표의 픽셀값이 커지므로 밝아진다.
- n이 음수이면 각 좌표의 픽셀값이 작아지므로 어두워 진다. - cv2.add(src1, src2)
- 밝기 조절이나 두 영상을 합치기에 사용할 수 있다.
- src1: 첫번째 영상 또는 스칼라
- src2: 두번째 영상 또는 스칼라
- src2를 src1에 더한다. - pixel에 스칼라 값을 더하거나 빼면 0 ~ 255의 범위를 넘어설 수 있다. ndarray의 type은 uint8(unsigned int8) 로 0 ~ 255범위를 넘어가는 숫자는 순환되어 버린다.
- 계산결과가 0이하가 되면 255로 부터 작아지게 되고 255가 넘어가면 0으로 가서 커지게 된다.
- 그래서 cv2.add() 함수는 값이 순환되지 않도록 0이하의 값은 0으로 255이상의 값은 255가 되도록 처리한다. 이것을 saturate연산이라고 한다.
import cv2
import numpy as np
img = cv2.imread('images/penguin.jpg', cv2.IMREAD_GRAYSCALE)
result1 = img - 100 # 어둡게
result2 = img + 100 # 밝게
cv2.imshow('src', img)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey()
cv2.destroyAllWindows()
원본과 더 밝게(+100) 만든 result2의 값 변화 히스토그램으로 관찰
hist1 = cv2.calcHist([img], [0], None, [256], [0, 256])
hist2 = cv2.calcHist([result2], [0], None, [256], [0, 256])
plt.plot(hist1.flatten(), label = '원본')
plt.plot(hist2.flatten(), label = '+100')
plt.legend()
plt.show()
0 ~ 255의 범위를 넘어가버리는 연산이 되는 값들은 더 밝아져야 할 값(255에 가깝게 되야할)이 어두워져버리는(0에 가깝게) 현상이 발생한다. -> saturation 연산의 필요성
saturaion 연산
# saturation 연산 처리
img = cv2.imread('images/penguin.jpg', cv2.IMREAD_GRAYSCALE)
result1 = cv2.add(img, - 100) # 어둡게
result2 = cv2.add(img, + 100) # 밝게
cv2.imshow('src', img)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey()
cv2.destroyAllWindows()
원본과 더 밝게(+100) 만든 result2의 값, 더 어둡게(-100)만든 result1의 값 변화 히스토그램으로 관찰
hist1 = cv2.calcHist([img], [0], None, [256], [0, 256])
hist2 = cv2.calcHist([result2], [0], None, [256], [0, 256])
hist3 = cv2.calcHist([result1], [0], None, [256], [0, 256])
plt.plot(hist1.flatten(), label = '원본')
plt.plot(hist2.flatten(), label = '+100')
plt.plot(hist3.flatten(), label = '-100')
plt.legend()
plt.show()
원하는 결과가 출력되는 것을 확인할 수 있다.
numpy함수를 이용
np.clip(연산, min, max)
연산결과가 min보다 작은 값들은 min으로, max보다 큰 값들은 max로 변경.
# bool < uint < int < float < str
# img - 100.0 # uint8 -> float64 ==> float64 - float64
# saturation 연산 처리 - numpy 함수를 이용
img = cv2.imread('images/penguin.jpg', cv2.IMREAD_GRAYSCALE)
# 1. 계산시 float으로 변환후 계산
# 2. np.clip() 사용. 0 ~ 255 범위로 조정
# 3. float -> uint8 타입으로 변환.
result1 = np.clip(img-100.0, 0, 255).astype('uint8')
result2 = np.clip(img+100.0, 0, 255).astype('uint8')
cv2.imshow('src', img)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey()
cv2.destroyAllWindows()
이미지 두개 합치기
# 두 이미지 합치기
lenna_gray = cv2.imread('images/Lenna.png', cv2.IMREAD_GRAYSCALE)
img2 = cv2.resize(img, (512, 512))
result = cv2.add(lenna_gray, img2) # element-wise 연산 # image + image = 두 이미지를 겹친다.(합친다.)
cv2.imshow('result', result)
cv2.waitKey()
cv2.destroyAllWindows()
컬러이미지 밝기 조절하기 (HSV 모드)
# 컬러이미지 밝기 조절 -> HSV 색공간으로 변환. H: 색, S: 채도, V: 명암(밝기)
lenna = cv2.imread('images/Lenna.png')
lenna_hsv = cv2.cvtColor(lenna, cv2.COLOR_BGR2HSV)
lenna_hsv[:, :, 2] = cv2.add(lenna_hsv[:, :, 2], -100)
cv2.imshow('src', lenna)
cv2.imshow('result', cv2.cvtColor(lenna_hsv, cv2.COLOR_HSV2BGR))
cv2.waitKey()
cv2.destroyAllWindows()
컬러이미지 밝기 조절하기 (BGR 모드)
# BGR 모드에서 덧셈을 처리
# channel이 있는 경우 -> channel 별로 계산. (0, 1, 2, 3)
# 상수를 지정한 경우: 0번 channel이랑만 계산한다.
result = cv2.add(lenna, (100, 100, 100, 0))
cv2.imshow('src', lenna)
cv2.imshow('result', result)
cv2.waitKey()
cv2.destroyAllWindows()
명암비(Contrast) 조정
명암비/대비(Contrast)란
- 밝은 부분과 어두분 부분 사이의 밝기의 정도 차이
- 영상이 전체적으로 밝은 픽셀들로만 구성되거나 어두운 픽셀들로만 구성되면 명암비가 낮다고 표현한다.
- 영상에 밝은 영역과 어두운 영역이 골고루 섞여있으면 명암비가 높다고 표현한다. - 명암비가 낮으면 객체간의 구분이 되지 않아 흐릿한 느낌이 나고 명암비가 높으면 선명한 느낌이 든다.
- 명암비를 변환하는 함수공식
- g(x, y)=f(x,y)+(f(x,y)-128)alpha == (1+alpha)f(x, y)-alpha 128 - g는 결과 image
- f는 원본 image
- x: x좌표
- y: y좌표
- alpha: 대비를 조절하는 값.
- 0: 원본과 동일
- 음수: 명암비를 낮춘다.
- 양수: 명암비를 높인다.
명암비 변환 함수공식 함수 정의
def control_contrast(img, alpha = 0.0):
return np.clip((1.0 + alpha) * img - alpha * 128, 0 , 255).astype('uint8')
원본과 명암비를 높인것과 낮춘것의 그래프 그리기
a1 = np.arange(256)
a2 = control_contrast(a1, alpha = -0.5)
a3 = control_contrast(a1, alpha = 1.0)
plt.plot(a1, linestyle = ':', label = '0')
plt.plot(a2, label = '-0.5')
plt.plot(a3, label = '1')
plt.legend(title = 'alpha')
plt.show()
gray scale 명암비 조정
# gray scale
lenna_gray = cv2.imread('images/Lenna.png', cv2.IMREAD_GRAYSCALE)
result1 = control_contrast(lenna_gray, -0.5)
result2 = control_contrast(lenna_gray, 1.0)
원본과 명암비를 높인것과 낮춘것 차이 확인
plt.figure(figsize = (15, 5))
plt.subplot(1, 3, 1)
plt.imshow(lenna_gray, cmap = 'gray')
plt.title('원본')
plt.subplot(1, 3, 2)
plt.imshow(result1, cmap = 'gray')
plt.title('-0.5')
plt.subplot(1, 3, 3)
plt.imshow(result2, cmap = 'gray')
plt.title('1.0')
plt.tight_layout()
plt.show()
채도 (Saturation) 변환
- 채도: 색의 선명도를 말한다.
- 채도를 변환할 때는 HSV color 타입으로 변환한 뒤 S의 값을 바꾼다.
HSV : 색상(Hue), 채도(Saturation), 명도(Value)
H -> 0, S -> 1, V -> 2
채도 S는 1에 있기 때문에 1을 변경
lenna = cv2.imread('images/Lenna.png')
lenna_hsv = cv2.cvtColor(lenna, cv2.COLOR_BGR2HSV)
value = 50
lenna_hsv[:, :, 1] = cv2.add(lenna_hsv[:, :, 1], value)
원본과 채도를 변경한 것의 차이 확인
plt.figure(figsize = (10, 5))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(lenna, cv2.COLOR_BGR2RGB))
plt.title('원본')
plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(lenna_hsv, cv2.COLOR_HSV2RGB))
plt.title(f"{value}")
plt.tight_layout()
plt.show()
Filter
Filtering 개요
- 영상에서 필요한 정보만 통과시키고 필요없는 정보는 걸러내는 작업을 말한다.
- N x N행렬을 영상과 Convolution연산(Correlation연산)을 하여 처리한다.
- 영상과 연산하는 N x N 행렬을 커널(Kernel), 필터(filter), 마스크(Mask), 윈도우(Window)라고 한다.
- Convolution연산
- Filter를 영상을 이동하면서 같은 index의 값끼리 곱한뒤 다 더한다.
- 최외곽처리는 바깥에 가상의 픽셀들이 있다고 생각하고 거기에 임의의 값을 채워 계산한다.
- 가상의 바깥픽셀을 Padding이라고 한다.
- 바깥픽셀을 채우는 방법
- BORDER_CONSTANT : 같은 값으로 채운다. 보통 0으로 채움 000|abcd|000
- BORDER_REPLICATE: 외곽에 있는 값으로 채운다. aaa|abcd|ddd
- BORDER_REFLECT: 거울에 반사되는 형식으로 채운다. cba|abcd|dcb
- BORDER_REFLECT_101: (OpenCV 기본방식)BORDER_REFLECT와 같은 형식인데 가장 외곽의 값은 반사시키지 않는다. dcb|abcd|cba
- N x N 행렬이 어떤 값을 가지고 있느냐에 따라 다양한 영상처리가 가능하다.
- Bluring: 영상을 부드럽게 만들기
- Shapening: 영상을 날카롭게 만들기
- 엣지 검출
- Noise 제거
- 다양한 형태의 Kernel이 있으나 보통 3 x 3 행렬을 많이 쓴다
- Anchor(고정점): 영상에서 Filtering을 하려는 지점을 가리키는 필터의 중앙점을 사용한다.
- Receptive Field(수용장): 결과 픽셀에 영향을 주는 원본 이미지의 영역
기본적인 2차원 Filter계산
- cv2.filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None)
- src: 입력영상
- ddepth: 출력 영상데이터의 타입 (opencv제공 변수 사용. cv2.CV_8U, cv2.CV_32F), -1 지정하면 입력영상과 같은 타입의 결과를 출력
- kernel: Filtering을 수행할 kernel 행렬. (실수형)
- anchor: kernel의 achor 위치. (-1,-1) 지정하면 kernel의 중앙을 사용
- delta: 추가적으로 더할값.
- borderType: padding 타입
- 반환값
- Filtering된 결과영상
Blurring
- 영상을 흐릿/부드럽게 만드는 작업.
- 픽셀간의 값의 차이를 줄여서 날카로운 엣지가 무뎌지고 영상에 있는 Noise(잡음)이 사라지도록 하는 효과가 있다.
- 엣지(Edge): 물체간의 경계부분으로 contrast(대비)가 크다.
평균값 블러링(Average Blur)
- 주변 픽셀의 평균값을 합성곱하여 적용한다.
- 보통 3 x 3이나 5 x 5 필터를 많이 쓰며 필터의 크기가 커질수록 더 흐릿하게 변환된다.
- 영상이 뭉게져 세밀한 부분이 잘 안보이는 단점이 있다.
- cv2.blur(src, ksize)
- src: blur 처리할 입력영상
- ksize: 필터의 크기. (width, height) 튜플로 지정
이미지 불러오기
import cv2
import numpy as np
import matplotlib.pyplot as plt
lenna = cv2.imread('images/Lenna.png')
cv2.blur(src, ksize)
lenna_blur = cv2.blur(lenna, (3, 3)) # 3 x 3 : 모든 값이 1/9
lenna_blur2 = cv2.blur(lenna, (5, 5)) # 5 x 5 : 1/25
# 필터의 크기가 커질수록 더 흐릿해진다.
cv2.imshow("src", lenna)
cv2.imshow('result', lenna_blur)
cv2.imshow('result2', lenna_blur2)
cv2.waitKey()
cv2.destroyAllWindows()
noise 감소 확인
- noise는 pixcel값에 차이가 큰 픽셀이 중간 중간 껴있는 상태
- ex) 200, 200, 5, 200, 10, 200, 200
plt.figure(figsize = (10, 3))
plt.plot(lenna[30,:,2])
plt.show()
# 급경사는 edge일 가능성이 높다
blur처리한 것의 그래프는 edge는 있지만 다른값들의 차이(noise)를 적게 만들어 준다.
plt.figure(figsize = (10, 3))
plt.plot(lenna_blur[30,:,2])
plt.show()
filter2D() 함수 이용
# filter2D() 함수 이용 -> 직접 filter를 전달해서 convolution 연산을 처리
# 3 X 3 필터
kernel = np.ones(shape = (3, 3)) / 9
result = cv2.filter2D(lenna, -1, kernel)
cv2.imshow("result", result)
cv2.waitKey()
cv2.destroyAllWindows()
Gaussian Blur
- 대상 픽셀(Anchor)와 가까운 픽셀은 큰 가중치 멀리있는 픽셀은 작은 가중치를 사용해서 평균(가중평균)을 계산해서 필터링 한다.
- Gaussian 분포(정규분포)의 확률밀도 함수를 사용해 필터에 들어갈 값들을 계산하여 대상픽셀을 기준으로 멀어질 수록 작은 값이 곱해지도록 한다.
- 평균은 0으로 하고 표준편차를 조정하여 흐림의 정도를 조절한다. 표준편차 값을 크게할 수록 흐려진다.
- Filter의 shape은 (8sigma + 1,8sigma + 1) 나 (6sigma + 1,6sigma + 1) 으로 정해진다. sigma: 표준편차 - Gaussian Blurring은 blur효과 뿐아니라 노이즈제거에도 많이 사용된다.
- cv2.GaussianBlur(src, ksize, sigmaX, sigmaY)
- src: blur를 적용할 영상.
- ksize: 커널크기. (0,0)으로 지정하면 sigma 값에 의해 결정된다. (보통 0,0 으로 설정)
- sigamX: X축방향 sigma(표준편차) 값
- sigamY: Y축방향 sigma(표준편차) 값. 생략하면 sigmaX와 같은 값 사용
import cv2
lenna = cv2.imread("images/Lenna.png")
lenna_gb = cv2.GaussianBlur(lenna,
ksize=(0, 0), # 커널(필터) 크기: (0, 0) -> 표준편차에 크기에 맞춰 크기를 정한다.
sigmaX = 1, # 커널크기: (W, H) => Width 표준편차
#sigmaY = 1, # Height 표준편차. 생략-sigmaX와 동일한 값
)
# 표준편차를 크게할 수록 필터 크기가 커진다. -> 커지면 blur 효과가 강해진다.
cv2.imshow("src", lenna)
cv2.imshow("result", lenna_gb)
cv2.waitKey()
cv2.destroyAllWindows()
잡음제거
- cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)
- 양방향 필터로 선명도는 유지하면서 bluring을 이용해 노이즈를 제거한다.
- src: 잡음을 제거할 입력영상
- d: 필터의 크기 지정(가우시안 blur의 ksize). 음수(-1)을 입력하면 sigmaSpace값에 의해 자동 결정된다. 일반 적으로 -1을 설정한다.
- sigmaColor
- 엣지인지 아닌지를 판단하는 기준값 설정. pixcel간의 차이가 지정한 값보다 크면 엣지라고 생각하고 작으 면 엣지가 아니라고 생각해 그 지점을 blurring 한다.
- 이 값을 너무 크게 주면 엣지가 검출이 안되서 그냥 Gaussian Filter 사용하는 것과 같다.
- sigmaSpace: Gaussian Filter에서 지정한 표준편차
sigma = 10
sigmaColor = 30
lenna_bf = cv2.bilateralFilter(lenna,
d=-1, # filter크기를 표준편차로 결정.
sigmaColor=sigmaColor, # 엣지로 판단할 pixcel값의 차이.
sigmaSpace=sigma, # 표준편차
)
결과값 확인
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 7))
plt.subplot(1, 2, 1)
plt.imshow(lenna[:,:,::-1])
plt.subplot(1,2,2)
plt.imshow(lenna_bf[:, :, ::-1])
plt.title(f"잡음제거: sigma={sigma}")
plt.tight_layout()
plt.show()
원본 lenna와 잡음이 제거된 lenna_bf의 그래프 확인
- 그래프의 모양을 보면 잡음 제거된 그래프에서 매끈하게 이루어진 것을 확인할 수 있다.
plt.figure(figsize=(20,6))
plt.subplot(2, 1, 1)
plt.plot(lenna[30, :, 0])
plt.subplot(2, 1, 2)
plt.plot(lenna_bf[30, :, 0])
plt.show()
샤프닝(Sharppen)
- Blurring의 반대로 흐린영상을 선명한 영상으로 만드는 작업.
- 선명한 영상을 만들기 위해서는 이미지의 엣지(edge-사물의 윤곽부분)의 대비를 크게 만들어 준다.
- 이미지 전체를 다 선명하게 하는 게 아니라 윤곽(edge)만 선명하게 만들어 명암비(contrast)를 크게해 선명하게 보이게 한다. - Unsharp mask(언샤프 마스크) 필터링
- Unsharp한 영상을 이용해 Sharp한 영상을 만든다고 해서 Unsharp mask filtering 이라고 한다.
- 원본 이미지에서 blurring 한 이미지를 뺀다음 그것을 원본이미지에 다시 더한다.
h(x) = f(x)+alpha(f(x)-bar{f}(x))f(x)

- 제공 함수는 없고 위 공식을 구현한다.
- addWeighted(src, src_가중치, src2, src2_가중치, 더해줄값)
- 가중합 계산 함수
- src * src가중치 + src2 * src2가중치 + 더해줄값
import cv2
import numpy as np
import matplotlib.pyplot as plt
def sharppen(src_img, alpha=0.0):
# 원본 + (원본 - 블러) * alpha
# blur -> 가우시안 블러
src_blur = cv2.GaussianBlur(src_img, (0, 0), 1)
src_img = src_img.astype('float64')
return np.clip(src_img + (src_img - src_blur) * alpha, 0, 255).astype('uint8')
def sharppen2(src_img, alpha=0.0):
src_blur = cv2.GaussianBlur(src_img, (0, 0), 1)
return cv2.addWeighted(src_img, (1+alpha), src_blur, -alpha, 0)
rose = cv2.imread("images/rose.bmp")
rose_1 = sharppen(rose, 1.0)
rose_2 = sharppen(rose, 2.0)
rose_3 = sharppen(rose, 3.0)
결과 확인
- 원본과 rose_3의 그림들의 중심과 외곽부분의 명암대비를 살펴보면 rose_3의 그림이 더 대비가 명확하다.
plt.figure(figsize=(13, 10))
plt.subplot(2, 2, 1)
plt.imshow(rose[:,:,::-1])
plt.title("원본")
plt.subplot(2, 2, 2)
plt.imshow(rose_1[:,:,::-1])
plt.title("alpha 1")
plt.subplot(2, 2, 3)
plt.imshow(rose_2[:,:,::-1])
plt.title("alpha 2")
plt.subplot(2, 2, 4)
plt.imshow(rose_3[:,:,::-1])
plt.title("alpha 3")
plt.tight_layout()
plt.show()