영상처리(Image Processing)
- 입력 받은 영상을 사용 목적에 맞게 적절하게 처리하여 보다 개선된 영상을 생성하는 것
- 입력 영상에 있는 잡음(noise) 제거, 영상의 대비(contrast) 개선, 관심영역(region of interest) 강조, 영역 분할(segmentation), 입축 및 저장 등
- 저수준 영상 처리(좁은 의미의 영상 처리)
- 영상 획득
- 영상 향상
- 영상 복원
- 변환 처리
- 영상 압축
- 고수준 영상 처리(컴퓨터 비전)
- 영상 분할
- 영상 표현
- 영상 인식
영상처리의 역사
- 영상처리의 시작
- 1920년대 초반 런던과 뉴욕 간에 해저 케이블을 통한 신문사들이 사진 전송
- 본격적인 영상 처리 위한 기술
- 1940년대 폰 노이만의 디지털 컴퓨터의 개념 시작
- 1950년 이후 트랜지스터, IC, 마이크로프로세서 같은 하드웨어 발달
- 1950 ~ 60년대 프로그램 언어의 발달과 운영체제 등의 소프트웨어 기술 발달
- 본격적인 영상 처리 시작
- 우주 탐사 계획인 아폴로 계획과도 관련, 우주선에서 보낸 훼손된 영상의 복원 연구
- 1970년대 영상 처리 분야 더욱 발전
- CT, MRI 등의 의료 분야
- 원격 자원 탐사, 우주 항공 관련 분야
- 1990년대 컴퓨터 비전과 응용 분야 급속히 확장
- 인터넷 시대에 영상검색, 영상전송, 영상광고
- 디지털 방송 관련 컴퓨터 그래픽스, 디지털 카메라 보급
영상처리 응용 분야
- 의료 분야(방사선, 초음파
- 컴퓨터 단층 촬영(CT), 자기 공명 영상(MRI)
- 양전자 단층 촬영(PET)
- 방송 통신 분야
- 디지털 방송 서비스로 인한 영상처리 기술 발달
- 스포츠 방송 분야에 영상 처리 기술 적용, 가상 광고 분야
- 공장 자동화 분야
- 산업용 카메라로 제품 품질 모니터링 및 불량 제거
- 기상 및 지질 탐사 분야
- 방대한 기상 정보를 이용의 시각화
- 다양한 주파수의 사진들을 영상 처리 기술로 표현
- 애니메이션 및 게임 분야
- 촬영된 영상과 그래픽 기술이 조합
- 현실감 향상
- 출판 및 사진 분야
- 영상 생성, 품질 향상, 색상을 조작 등의 작업을 위해 영상 처리 기술 사용
- 기존 영상에 영상 처리 기술을 융합하여 새로운 합성 영상
컴퓨터 비전 처리 단계
- 전처리 단계
- 주로 영상처리 기술 사용
- 다양한 특징 추출 : 엣지(edge), 선분, 영역, SIFT(Scale-Invariant Feature Transform) 등
- 고수준 처리
- 특징 정보를 사용하여 영상을 해석, 분류, 상황 묘사 등 정보 생성
화소(Pixel, 畵素)
- 디지털 영상을 표현하는 2차원 배열에서 각 원소
- 해당 위치에서 빛의 세기에 대응하는 값
- 0은 검은색을 나타내고, 화소값이 커질수록 밝은색
- 컬러 영상
- R(red), G(green), B(blue) 세 가지 색상에 대한 화소 정보 표현
- 3개의 값을 가진 2차원 행렬로 표현
- 화소를 처리하는 것이 영상 처리의 시작
이미지와 색공간
- 색 : 빛에서 주파수(파장)의 차이에 따라 다르게 느껴지는 색상
- 가시광선 : 전자기파 중에서 인간이 인지할 수 있는 약 380nm ~ 780nm 사이의 파장
- 0 ~ 255 사이의 값으로 밝기를 표현
- color : 3차원(true color 라고도 불림)
- gray scale : 2차원
- 0 ~ 255의 값을 통해 밝기를 표현
- 0으로 갈수록 어두워지고, 255로 갈수록 밝아짐
이미지 파일 형식
- BMP
- 픽셀 데이터를 압축하지 않은 상태로 저장
- 파일 구조 간단하지만 용량이 매우 큼
- JPG(JPEG)
- 손실 압축(lossy compression) 사용
- 원본 영상으로부터 픽셀값이 미세하게 달라짐
- 파일 용량 크기가 크게 감소하는 이점
- 디지털 카메라
- GIF
- 무손실 압축(losses compression)
- 움직이는 그림인 Animation GIF 지원
- 256 이하의 색상을 가진 영상만을 저장하고, 화질이 매우 떨어짐
- PNG
- Portable Network Graphics
- 무손실 압축 사용
- 용량은 큰 편이지만 픽셀값이 변경되지 않음
- α 채널을 지원하여 일부분을 투명하게 설정 가능
OpenCV
- 실시간 컴퓨터 비전을 목적으로 인텔(Intel)에서 개발
- 실시간 이미지 프로세싱에 중점을 둔 한 프로그래밍 라이브러리
- TensorFlow, PyTorch 및 Caffe의 딥러닝 프레임워크 지원
이미지 읽기/쓰기
- 이미지는 배열로 표현 가능(Numpy)
이미지 읽기(PIL)
pillow
,matplotlib
와OpenCV
모두 가능- 구글 코랩(Colab), 주피터 노트북과 같은 환경에서는 pillow(PIL), matplotlib이 더 적합
- OpenCV는 주로 파이썬 스크립트 환경에서 사용
from google.colab.patches import cv2_imshow
으로 이미지는 출력할 수 있지만 동영상 관련 처리 불가
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from PIL import Image
import requests
from io import BytesIO
url = 'https://vrthumb.imagetoday.co.kr/2019/11/19/twi001t2920297.jpg'
response = requests.get(url)
pic = Image.open(BytesIO(response.content))
이미지 출력(PIL)
- 타입(type) 확인
- PIL 이미지를 array형으로 형변환
np.asarray()
pic
type(pic)
PIL.JpegImagePlugin.JpegImageFile
pic_arr = np.asarray(pic)
type(pic_arr)
numpy.ndarray
pic_arr.shape
(340, 604, 3)
pic_arr
array([[[217, 175, 177],
[218, 176, 180],
[219, 177, 181],
...,
[159, 119, 146],
[159, 119, 146],
[160, 117, 145]],
[[218, 176, 178],
[219, 177, 179],
[220, 178, 180],
...,
[154, 114, 141],
[154, 114, 141],
[152, 112, 139]],
[[216, 174, 175],
[217, 175, 176],
[218, 176, 177],
...,
[153, 112, 142],
[153, 112, 142],
[151, 110, 140]],
...,
[[ 33, 139, 187],
[ 61, 168, 210],
[ 80, 176, 237],
...,
[170, 212, 237],
[137, 195, 219],
[105, 186, 205]],
[[185, 208, 252],
[161, 211, 244],
[ 89, 166, 208],
...,
[179, 207, 244],
[162, 200, 239],
[118, 183, 225]],
[[101, 173, 197],
[ 67, 149, 170],
[ 38, 121, 163],
...,
[ 85, 175, 209],
[144, 200, 233],
[149, 205, 232]]], dtype=uint8)
이미지 출력(matplotlib)
- R, G, B 에 따라 이미지 확인
- 채널 순서 (R G B : 0 1 2)
R channel
G channel
B channel
plt.imshow(pic_arr)
plt.show()
pic_copy = pic_arr.copy()
plt.imshow(pic_copy)
plt.show()
pic_copy.shape
(340, 604, 3)
print(pic_copy[:,:,0])
print(pic_copy[:,:,0].shape)
[[217 218 219 ... 159 159 160]
[218 219 220 ... 154 154 152]
[216 217 218 ... 153 153 151]
...
[ 33 61 80 ... 170 137 105]
[185 161 89 ... 179 162 118]
[101 67 38 ... 85 144 149]]
(340, 604)
Red
plt.imshow(pic_copy[:,:,0])
plt.show()
plt.imshow(pic_copy[:,:,0], cmap='gray')
plt.show()
Green
print(pic_copy[:,:,1])
print(pic_copy[:,:,1].shape)
[[175 176 177 ... 119 119 117]
[176 177 178 ... 114 114 112]
[174 175 176 ... 112 112 110]
...
[139 168 176 ... 212 195 186]
[208 211 166 ... 207 200 183]
[173 149 121 ... 175 200 205]]
(340, 604)
plt.imshow(pic_copy[:,:,1], cmap='gray')
plt.show()
Blue
print(pic_copy[:,:,2])
print(pic_copy[:,:,2].shape)
[[177 180 181 ... 146 146 145]
[178 179 180 ... 141 141 139]
[175 176 177 ... 142 142 140]
...
[187 210 237 ... 237 219 205]
[252 244 208 ... 244 239 225]
[197 170 163 ... 209 233 232]]
(340, 604)
plt.imshow(pic_copy[:,:,2], cmap='gray')
plt.show()
pic_red = pic_arr.copy()
pic_red[:,:,1] = 0
pic_red[:,:,2] = 0
pic_red
array([[[217, 0, 0],
[218, 0, 0],
[219, 0, 0],
...,
[159, 0, 0],
[159, 0, 0],
[160, 0, 0]],
[[218, 0, 0],
[219, 0, 0],
[220, 0, 0],
...,
[154, 0, 0],
[154, 0, 0],
[152, 0, 0]],
[[216, 0, 0],
[217, 0, 0],
[218, 0, 0],
...,
[153, 0, 0],
[153, 0, 0],
[151, 0, 0]],
...,
[[ 33, 0, 0],
[ 61, 0, 0],
[ 80, 0, 0],
...,
[170, 0, 0],
[137, 0, 0],
[105, 0, 0]],
[[185, 0, 0],
[161, 0, 0],
[ 89, 0, 0],
...,
[179, 0, 0],
[162, 0, 0],
[118, 0, 0]],
[[101, 0, 0],
[ 67, 0, 0],
[ 38, 0, 0],
...,
[ 85, 0, 0],
[144, 0, 0],
[149, 0, 0]]], dtype=uint8)
plt.imshow(pic_red)
plt.show()
pic_green = pic_arr.copy()
pic_green[:,:,0] = 0
pic_green[:,:,2] = 0
plt.imshow(pic_green)
plt.show()
pic_blue = pic_arr.copy()
pic_blue[:,:,0] = 0
pic_blue[:,:,1] = 0
plt.imshow(pic_blue)
plt.show()
이미지 출력(OpenCV)
from google.colab.patches import cv2_imshow
- 원래는
cv2.imshow
- 원래는
from google.colab.patches import cv2_imshow
cv2_imshow(pic_arr)
OpenCV의 채널 순서
- OpenCV를 통해 영상(이미지)을 다룰 때의 채널 순서는 B G R
- matplotlib은 R G B 순서
cv2.cvtColor()
- image array, 변경할 색공간을 인자로 넣어줌
- 변경할 색공간은 여러가지가 있음
cv2.COLOR_BGR2RGB
cv2.COLOR_RGB2GRAY
cv2.COLOR_GRAY2RGB
- (참고)array[:,:,::-1]을 통해서도 인덱스 순서를 바꿀 수 있음
image = cv2.cvtColor(pic_arr, cv2.COLOR_RGB2BGR)
cv2_imshow(image)
print(image[0][0])
print(pic_arr[0][0])
[177 175 217]
[217 175 177]
temp_arr = pic_arr[:,:,::-1]
print(pic_arr[0][0])
print(temp_arr[0][0])
[217 175 177]
[177 175 217]
이미지 읽기(OpenCV)
cv2.imread()
- path, 이미지 파일의 flag값을 인자로 넣어줌
cv2.IMREAD_COLOR
: 이미지 파일을 Color로 읽어들이고, 투명한 부분은 무시되며, Default 값cv2.IMREAD_GRAYSCALE
: 이미지를 Grayscale로 읽음. 실제 이미지 처리시 중간단계로 많이 사용cv2.IMREAD_UNCHANGED
: 이미지 파일을 alpha channel(투명도)까지 포함하여 읽어 드림- (주의)
cv2.imread()
는 잘못된 경로로 읽어도NoneType
으로 들어갈 뿐, 오류를 발생하지 않음
!wget -O lion.jpg https://cdn.pixabay.com/photo/2017/10/25/16/54/african-lion-2888519__480.jpg
--2021-10-25 09:48:34-- https://cdn.pixabay.com/photo/2017/10/25/16/54/african-lion-2888519__480.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.21.183, 104.18.20.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.21.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 65101 (64K) [image/jpeg]
Saving to: ‘lion.jpg’
lion.jpg 100%[===================>] 63.58K --.-KB/s in 0.01s
2021-10-25 09:48:34 (5.16 MB/s) - ‘lion.jpg’ saved [65101/65101]
img = cv2.imread('/content/lion.jpg', cv2.IMREAD_UNCHANGED)
print(type(img))
<class 'numpy.ndarray'>
cv2_imshow(img)
plt.imshow(img)
plt.show()
img_temp = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_temp)
plt.show()
img_gray = cv2.imread('/content/lion.jpg', cv2.IMREAD_GRAYSCALE)
print(img_gray.shape)
(480, 644)
cv2_imshow(img_gray)
plt.imshow(img_gray)
plt.show()
plt.imshow(img_gray, cmap='gray')
plt.show()
plt.imshow(img_gray, cmap='magma')
plt.show()
이미지 쓰기
cv2.imwrite()
- 경로, 이미지 배열을 인자로 받음
- 이미지 저장을 하면 True 반환
- 없는 이미지를 읽어도 type이 NoneType으로 들어갈 뿐 에러를 발생하지 않음
random_img = np.random.randint(0, 256, size=(200, 200, 3))
print(random_img.shape)
(200, 200, 3)
cv2.imwrite('./random_img.png', random_img)
True
my_img = cv2.imread('/content/random_img.png')
print(type(my_img))
print(my_img.shape)
<class 'numpy.ndarray'>
(200, 200, 3)
cv2_imshow(my_img)
도형 그리기
- 다양한 도형을 그릴 수 있음
- 도형을 그리는 좌표가 해당 범위를 넘어가면 이미지에 표현되지 않음
- 얼굴 검출 알고리즘: 영상 위에 검출한 얼굴 영역을 사각형이나 원으로 표시
- 차선 검출 알고리즘: 차선을 정확하게 검출했는지 확인하기 위해 도로 영상 위에 선으로 표시
img = np.zeros((512,512,3), np.uint8)
plt.imshow(img)
plt.show()
직선(Line) 그리기
cv.line()
파라미터 | 설명 |
---|---|
img |
그림을 그릴 이미지 |
start |
시작 좌표 |
end |
종료 좌표 |
color |
BGR 형태의 Color(e.g., (255,0,0)->Blue) |
thickness (int) |
선 두께(pixel) |
img = cv2.line(img, (0,0),(511,511),(255,0,0),5)
plt.imshow(img)
plt.show()
사각형(Reatangle) 그리기
cv.rectangle()
파라미터 | 설명 |
---|---|
img |
그림을 그릴 이미지 |
start |
시작 좌표 |
end |
종료 좌표 |
color |
BGR 형태의 Color |
thickness |
선 두께(pixel) |
img = cv2.rectangle(img, (400,0),(510,100),(0,255,0),3)
plt.imshow(img)
plt.show()
원(Circle) 그리기
cv.circle()
파라미터 | 설명 |
---|---|
img |
그림을 그릴 이미지 |
center |
원의 중심 좌표(x,y) |
radian |
반지름 |
color |
BGR 형태의 Color |
thickness |
선 두께, -1이면 원 안쪽을 채움 |
lineType |
선의 형태, cv.line() 함수의 인수와 동일 |
shift |
좌표에 대한 비트 시프트 연산 |
img = cv2.circle(img, (450,50), 50, (0,0,255), -1)
plt.imshow(img)
plt.show()
img = cv2.circle(img, (50,450), 50, (0, 255, 255), 2)
plt.imshow(img)
plt.show()
타원(Ellipse) 그리기
cv.ellipse()
파라미터 | 설명 |
---|---|
img |
그림을 그릴 이미지 |
center |
원의 중심 좌표(x,y) |
axes |
중심에서 가장 큰 거리와 작은 거리 |
angle |
타원의 기울기 각 |
startAngle |
타원의 시작 각도 |
endAngle |
타원이 끝나는 각도 |
color |
타원의 색 |
thickness |
선 두께, -1이면 원 안쪽을 채움 |
lineType |
선의 형태 |
shift |
좌표에 대한 비트 시프트 |
img = cv2.ellipse(img, (256,256), (150,30), 0, 0, 180, (0,255,0), -1)
plt.imshow(img)
plt.show()
img = cv2.ellipse(img, (256,256), (150,50), 45, 0, 360, (255,255,255), 2)
plt.imshow(img)
plt.show()
img = cv2.ellipse(img, (256,256), (150,10), 135, 0, 270, (0,0,255), 2)
plt.imshow(img)
plt.show()
pts = np.array([[10,5], [20,30], [70,20], [50,10]], np.int32)
print(pts.shape)
pts
(4, 2)
array([[10, 5],
[20, 30],
[70, 20],
[50, 10]], dtype=int32)
pts = pts.reshape((-1, 2, 1))
print(pts.shape)
pts
(4, 2, 1)
array([[[10],
[ 5]],
[[20],
[30]],
[[70],
[20]],
[[50],
[10]]], dtype=int32)
다각형(Polygon) 그리기
cv.polylines()
파라미터 | 설명 |
---|---|
img |
그림을 그릴 이미지 |
pts (array) |
연결할 꼭지점 좌표 |
isClosed |
닫힌 도형 여부 |
color |
다각형의 색 |
thickness |
선 두께 |
- 이미지에 표현하기 위해 점 좌표를 3차원 행렬로 변환
- 변환 이전과 이후의 행렬 갯수는 동일해야함
- -1은 원본에 해당하는 값을 그대로 유지
img = cv2.polylines(img, [pts], True, (0, 150, 250), 4)
plt.imshow(img)
plt.show()
pts2 = np.array([[150,5], [200,30], [100,70], [50,20]], np.int32)
print(pts2.shape)
pts = pts.reshape((-1, 2, 1))
print(pts.shape)
pts
(4, 2)
(4, 2, 1)
array([[[10],
[ 5]],
[[20],
[30]],
[[70],
[20]],
[[50],
[10]]], dtype=int32)
img = cv2.polylines(img, [pts2], True, (172, 200, 255), 4)
plt.imshow(img)
plt.show()
텍스트(Text) 그리기
cv.putText()
파라미터 | 설명 |
---|---|
img |
그림을 그릴 이미지 |
text |
표시할 문자열 |
org |
문자열이 표시될 위치. 문자열의 bottom-left corner 점 |
fontFace |
폰트 타입. CV2.FONT_XXX |
fontScale |
폰트 크기 |
color |
폰트 색 |
thickness |
글자의 굵기 |
lineType |
글자 선의 형태 |
bottomLeftOrigin |
영상의 원점 좌표 설정(True: 좌하단, False: 좌상단) |
- 문자열 폰트 옵션
옵션 | 값 | 설명 |
---|---|---|
cv2.FONT_HERSHEY_SIMPLEX |
0 | 중간 크기 산세리프 폰트 |
cv2.FONT_HERSHEY_PLANIN |
1 | 작은 크기 산세리프 폰트 |
cv2.FONT_HERSHEY_DUPLEX |
2 | 2줄 산세리프 폰트 |
cv2.FONT_HERSHEY_COMPLEX |
3 | 중간 크기 세리프 폰트 |
cv2.FONT_HERSHEY_TRIPLEX |
4 | 3줄 세리프 폰트 |
cv2.FONT_HERSHEY_COMPLEX_SMALL |
5 | COMPLEX 보다 작은 크기 |
cv2.FONT_HERSHEY_SCRIPT_SIMPLEX |
6 | 필기체 스타일 폰트 |
cv2.FONT_HERSHEY_SCRIPT_COMPLEX |
7 | 복잡한 필기체 스타일 |
cv2.FONT_ITALIC |
16 | 이탤릭체를 위한 플레그 |
img = cv2.putText(img, 'OpenCV', (10,500), cv2.FONT_HERSHEY_SIMPLEX, 4, (255,255,255),3)
plt.imshow(img)
plt.show()
컬러 매핑(Color Mapping)
- 주로 그레이 스케일(Grayscale), 트루 컬러(True Color, RGB) 이미지를 많이 활용
- 다양한 색 공간(ex,
HSV
,YCrCB
등)이 존재하고 이들을 변환할 수 있음 - 컬러 영상 처리에서
HSV
와HSL
은 같은 색 공간을 이용하여 색상 구분에 용이하고,YCrCB와
YUV`는 휘도 성분 구분에 용이 cv2.cvtColor()
활용
색 공간의 종류(참고)
- RGB
- 컬러 표현을 빛의 3원색인 빨강(Red), 초록(Green), 파랑(Blue)으로 서로 다른 비율을 통해 색 표현
- 가산 혼합(additive mixture): 빛을 섞을 수록 밝아짐
- 모니터, 텔레비전, 빔 프로젝터와 같은 디스플레이 장비들에서 기본 컬러 공간으로 사용
- CMYK
- 청록색(Cyan), 자홍색(Magenta), 노랑색(Yellow), 검은색(Black)을 기본으로 하여 주로 컬러 프린트나 인쇄시에 사용 감산 혼합(subtractive mixture): 섞을 수록 어두워지는 방식
- RGB 컬러 공간과 보색 관계
- YUV
- Y축은 밝기 성분을 U,V 두축을 이용하여 색상을 표현
- U축은 파란색에서 밝기 성분을 뺀 값, V축은 빨간색에서 밝기 성분을 뺀 값
- 아날로그 컬러신호 변환에 주로 사용. (U=B-Y), (V=R-Y)
- YCbCr
- Digital TV에서 사용하는 색공간
- YPbPr이라는 아날로그 신호의 색공간을 디지털화한 것
- YPbPr은 아날로그 컴포넌트 비디오에서 사용
RBG Color Space
- 디지털 컬러 영상을 획득할 때 사용
- 보편적으로 사용되고 있지만 컬러 영상 처리에서는 주로 사용되지 않음
!wget -O dog.jpg http://image.dongascience.com/Photo/2017/03/14900752352661.jpg
--2021-10-25 09:48:38-- http://image.dongascience.com/Photo/2017/03/14900752352661.jpg
Resolving image.dongascience.com (image.dongascience.com)... 211.43.210.30
Connecting to image.dongascience.com (image.dongascience.com)|211.43.210.30|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 61914 (60K) [image/jpeg]
Saving to: ‘dog.jpg’
dog.jpg 100%[===================>] 60.46K 78.7KB/s in 0.8s
2021-10-25 09:48:40 (78.7 KB/s) - ‘dog.jpg’ saved [61914/61914]
origin_img = cv2.imread('/content/dog.jpg')
print(origin_img.shape)
(447, 670, 3)
- OpenCV와 matplotlib의 색공간 순서가 다르기 때문에 생기는 문제
matplotlib
: R G BOpenCV
: B G R
plt.imshow(origin_img)
plt.show()
img_rgb = cv2.cvtColor(origin_img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()
HSV Color Space
- 색상(hue), 채도(saturation), 명도(value)로 색을 표현
- 색상은 흔히 빨간색, 노란색 등과 같은 색의 종류
- 채도는 색의 순도
- 파란색에서 채도가 높으면 맑고 선한 파란색
- 파란색에서 채도가 낮으면 탁한 파란색
- 명도는 빛의 세기
- 명도가 높으면 밝고, 낮으면 어둡게 느껴진다.
- OpenCV에서
BGR2HSV
색 공간 변환할 경우,H
: 0 ~ 179 사이의 정수로 표현- 색상 값은 0° ~ 360°로 표현하지만 uchar 자료형은 256이상의 정수를 표현할 수 없기 때문에 OpenCV에서는 각도를 2로 나눈 값을 H로 저장
S
: 0 ~ 255 사이의 정수로 표현V
: 0 ~ 255 사이의 정수로 표현
img_hsv = cv2.cvtColor(origin_img, cv2.COLOR_BGR2HSV)
plt.imshow(img_hsv)
plt.show()
print(np.max(img_hsv), np.min(img_hsv))
print(np.max(img_hsv[:,0,0]))
print(np.min(img_hsv[:,0,0]))
255 0
101
14
HSL Color Space
- 색상(hue), 채도(saturation), 밝기(lightness)로 색을 표현하는 방식
- HSV와 동일하지만 밝기 요소의 차이
- HSV와 더불어 사람이 실제로 color를 인지하는 방식과 유사
img_hsl = cv2.cvtColor(origin_img, cv2.COLOR_BGR2HLS)
plt.imshow(img_hsl)
plt.show()
print(np.max(img_hsl), np.min(img_hsl))
print(np.max(img_hsl[:,0,0]))
print(np.min(img_hsl[:,0,0]))
255 0
101
14
YCbCr Color Space
- Y 성분은 밝기 또는 휘도(luminance), Cb, Cr 성분은 색상 또는 색차(chrominance)를 나타냄
- Cb, Cr은 오직 색상 정보만 가지고 있음, 밝기 정보 X
- 영상을 GrayScale 정보와 색상 정보로 분리하여 처리할 때 유용
Y
,Cb
,Cr
: 0 ~ 255 사이의 정수로 표현
img_ycbcr = cv2.cvtColor(origin_img, cv2.COLOR_BGR2YCrCb)
plt.imshow(img_ycbcr)
plt.show()
print(np.max(img_ycbcr), np.min(img_ycbcr))
print(np.max(img_ycbcr[:,0,0]))
print(np.min(img_ycbcr[:,0,0]))
250 13
221
40
GrayScale Color Space
- 영상의 밝기 정보를 256단계(0 ~ 255)로 구분하여 표현
- 가장 밝은 흰색 : 255
- 가장 어두운 검은색 : 0
img_gray = cv2.cvtColor(origin_img, cv2.COLOR_BGR2GRAY)
plt.imshow(img_gray, cmap='gray')
plt.show()
print(np.max(img_gray), np.min(img_gray))
print(np.max(img_gray[:,0]))
print(np.min(img_gray[:,0]))
250 13
221
40
히스토그램(Histogram)
- 이미지의 밝기의 분포를 그래프로 표현한 방식
- 이미지의 전체를 밝기 분포와 채도(밝고 어두움)을 알 수 있음
용어 설명
- BINS
- 히스토그램 그래프의 X축의 간격
- 위 그림의 경우에는 0 ~ 255를 표현하였기 때문에 BINS 값은 256이 된다. BINS 값이 16이면 0 ~ 15, 16 ~ 31…, 240 ~ 255와 같이 X축이 16개로 표현
- OpenCV에서는 BINS를 histSize 라고 표현
- DIMS
- 이미지에서 조사하고자하는 값을 의미
- 빛의 강도를 조사할 것인지, RGB 값을 조사할 것인지를 결정
- RANGE
- 측정하고자하는 값의 범위
cv2.calcHist()
파라미터 설명 image
분석대상 이미지(uint8 or float32 type). Array 형태 channels
분석 채널(X축의 대상), 이미지가 grayscale이면 [0], color 이미지이면 [0],[0,1] 형태(1: Blue, 2: Green, 3: Red) mask
이미지의 분석 영역. None이면 전체 영역 histSize
BINS 값. [256] ranges
Range 값. [0,256]
!wget -O img1.jpg http://www.dailygaewon.com/news/photo/202105/11330_11828_3159.jpg
!wget -O img2.jpg http://image.kmib.co.kr/online_image/2020/0927/611718110015050456_1.jpg
--2021-10-25 09:48:41-- http://www.dailygaewon.com/news/photo/202105/11330_11828_3159.jpg
Resolving www.dailygaewon.com (www.dailygaewon.com)... 121.125.77.145
Connecting to www.dailygaewon.com (www.dailygaewon.com)|121.125.77.145|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 187985 (184K) [image/jpeg]
Saving to: ‘img1.jpg’
img1.jpg 100%[===================>] 183.58K 240KB/s in 0.8s
2021-10-25 09:48:43 (240 KB/s) - ‘img1.jpg’ saved [187985/187985]
--2021-10-25 09:48:43-- http://image.kmib.co.kr/online_image/2020/0927/611718110015050456_1.jpg
Resolving image.kmib.co.kr (image.kmib.co.kr)... 211.110.12.154
Connecting to image.kmib.co.kr (image.kmib.co.kr)|211.110.12.154|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 151740 (148K) [image/jpeg]
Saving to: ‘img2.jpg’
img2.jpg 100%[===================>] 148.18K 265KB/s in 0.6s
2021-10-25 09:48:45 (265 KB/s) - ‘img2.jpg’ saved [151740/151740]
img1 = cv2.imread('/content/img1.jpg', 0)
img2 = cv2.imread('/content/img2.jpg', 0)
hist1 = cv2.calcHist([img1], [0], None, [256], [0,256])
hist2 = cv2.calcHist([img2], [0], None, [256], [0,256])
plt.figure(figsize=(12,8))
plt.subplot(221)
plt.imshow(img1, 'gray')
plt.title('img1')
plt.subplot(222)
plt.imshow(img2, 'gray')
plt.title('img2')
plt.subplot(223)
plt.plot(hist1, color='r')
plt.plot(hist2, color='g')
plt.xlim([0,256])
plt.title('histogram')
plt.show()
Mask를 적용한 히스토그램
img = img1.copy()
print(img.shape)
(403, 600)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = np.zeros(img.shape[:2], np.uint8)
mask[180:300,260:400] = 255
masked_img = cv2.bitwise_and(img, img, mask=mask)
hist_full = cv2.calcHist([img], [1], None, [256], [0,256])
hist_mask = cv2.calcHist([img], [1], mask, [256], [0,256])
plt.figure(figsize=(10,10))
plt.subplot(221)
plt.imshow(img)
plt.title('Original Image')
plt.subplot(222)
plt.imshow(mask, 'gray')
plt.title('Mask')
plt.subplot(223)
plt.imshow(masked_img)
plt.title('Masked Image')
plt.subplot(224)
plt.title('Histogram')
plt.plot(hist_full, color='r')
plt.plot(hist_mask, color='b')
plt.xlim([0,256])
plt.show()
히스토그램 평탄화
- 이미지의 히스토그램이 특정 영역에 너무 집중되어 있으면 contrast 가 낮아 좋은 이미지라고 할 수 없음
- 전체 영역에 골고루 분포가 되어 있을 때 좋은 이미지라고 할 수 있는데, 아래 히스토그램을 보면 좌측 처럼 특정 영역에 집중되어 있는 분포를 오른쪽 처럼 골고루 분포하도록 하는 작업을 Histogram Equalization 이라고 함
- (참고) 이론적인 방법
- 이미지의 각 픽셀의 cumulative distribution(cdf)값을 구하고 Histogram Equalization 공식에 대입하여 0 ~ 255 사이의 값으로 변환
- 위 식으로 구해진 값을 이미지 표현하면 균일화된 이미지를 얻을 수 있음
- Numpy를 활용하여 균일화 작업
!wget -O img.jpg https://cdn.pixabay.com/photo/2021/10/14/13/50/book-6709160_960_720.jpg
--2021-10-25 09:48:46-- https://cdn.pixabay.com/photo/2021/10/14/13/50/book-6709160_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 84200 (82K) [image/jpeg]
Saving to: ‘img.jpg’
img.jpg 100%[===================>] 82.23K --.-KB/s in 0.01s
2021-10-25 09:48:46 (6.22 MB/s) - ‘img.jpg’ saved [84200/84200]
img = cv2.imread('/content/img.jpg', 0)
print(img.shape)
(562, 960)
hist, bins = np.histogram(img.flatten(), 256, [0,256])
cdf = hist.cumsum()
cdf_m = np.ma.masked_equal(cdf, 0)
cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min())
cdf = np.ma.filled(cdf_m, 0).astype('uint8')
img2 = cdf[img]
plt.figure(figsize=(10,8))
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(122)
plt.imshow(img2, 'gray')
plt.title('Equalization')
plt.show()
- OpenCV 함수로 간단하게 처리
img = cv2.imread('/content/img.jpg', 0)
print(img.shape)
(562, 960)
img2 = cv2.equalizeHist(img)
plt.figure(figsize=(10,8))
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(122)
plt.imshow(img2, 'gray')
plt.title('Equalization')
plt.show()
CLAHE(Contrast Limited Adaptive Histogram Equalization)
- 지금까지의 처리는 이미지의 전체적인 부분에 균일화를 적용
- 일반적인 이미지는 밝은 부분과 어두운 부분이 섞여 있기 때문에 전체에 적용하는 것은 그렇게 유용하지 않음
!wget -O img.jpg https://cdn.pixabay.com/photo/2015/08/13/01/00/keyboard-886462_960_720.jpg
--2021-10-25 09:48:47-- https://cdn.pixabay.com/photo/2015/08/13/01/00/keyboard-886462_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 54655 (53K) [image/jpeg]
Saving to: ‘img.jpg’
img.jpg 100%[===================>] 53.37K --.-KB/s in 0.01s
2021-10-25 09:48:47 (4.87 MB/s) - ‘img.jpg’ saved [54655/54655]
img = cv2.imread('/content/img.jpg', 0)
print(img.shape)
(540, 960)
img2 = cv2.equalizeHist(img)
plt.figure(figsize=(10,8))
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(122)
plt.imshow(img2, 'gray')
plt.title('Equalization')
plt.show()
- 위 결과에서 밝은 부분은 균일화가 적용되어 어두워졌지만, 일부 이미지 영역은 너무 어두워짐
- 이문제를 해결하기 위해서
adaptive histogram equalization
을 적용하게 됨- 즉, 이미지를 작은 tile 형태로 나누어 그 tile 안에서 Equalization을 적용하는 방식
- 작은 영역이다 보니 작은 노이즈(극단적으로 어둡거나, 밝은 영역)가 있으면 이것이 반영이 되어 원하는 결과를 얻을 수 없게 됨
- 이문제를 피하기 위해서
contrast limit
라는 값을 적용하여 이 값을 넘어가는 경우는 그 영역은 다른 영역에 균일하게 배분하여 적용
img = cv2.imread('/content/img.jpg', 0)
print(img.shape)
(540, 960)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img2 = clahe.apply(img)
plt.figure(figsize=(10,8))
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(122)
plt.imshow(img2, 'gray')
plt.title('Equalization')
plt.show()
2D Histogram
cv2.calcHist()
- 지금까지 Histogram은 1차원으로 grayscale 이미지의 pixel의 강도, 즉 빛의 세기를 분석한 결과
- 2D Histogram은 Color 이미지의 색상(hue) & 채도(saturation)을 동시에 분석하는 방법
- 색상과 채도를 분석하기 때문에 HSV Format으로 변환해야함
파라미터 | 설명 |
---|---|
image |
HSV로 변환된 이미지 |
channel |
0 -> Hue, 1 -> Saturation |
bins |
[180,256] 첫 번째는 Hue, 두번째는 Saturation |
range |
[0,180,0,256] Hue(0 ~ 180), Saturation(0 ~ 256) |
!wget -O img.jpg https://cdn.pixabay.com/photo/2017/08/16/00/59/panorama-2646143_960_720.jpg
--2021-10-25 09:48:48-- https://cdn.pixabay.com/photo/2017/08/16/00/59/panorama-2646143_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 79946 (78K) [image/jpeg]
Saving to: ‘img.jpg’
img.jpg 100%[===================>] 78.07K --.-KB/s in 0.01s
2021-10-25 09:48:49 (6.03 MB/s) - ‘img.jpg’ saved [79946/79946]
img = cv2.imread('/content/img.jpg')
print(img.shape)
(336, 960, 3)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0,1], None, [180,256], [0,180,0,256])
cv2_imshow(hist)
plt.imshow(hist)
plt.show()
- 위 Histogram을 보면 X축은 Saturation, Y축은 Hue 값을 나타냄
- Y축을 보면 100 근처에 값이 모여 있는 것을 알 수 있음
- HSV 모델에서 H가 100이면 하늘색
- 이 이미지는 하늘색이 많이 분포되어 있다는 것을 2D Histogram을 통해서 알 수 있음
이미지 처리(Image Processing)
- 필요에 따라 적절한 처리
resize()
,flip()
,getAffineTransform()
,warpAffine()
등 다양한 메서드 존재
Resize
cv2.resize()
- 사이즈가 변하면 pixel 사이의 값을 결정 해야함
- 보간법(Interpolation method)
- 사이즈를 줄일 때 :
cv2.INTER_AREA
- 사이즈를 크게 할 때 :
cv2.INTER_CUBIC
,cv2.INTER_LINEAR
파라미터 설명 img
Image dsize
Manual Size, 가로, 세로 형태의 tuple(e.g., (100,200)) fx
가로 사이즈의 배수, 2배로 크게 하려면 2, 반으로 줄이려면 0.5 fy
세로 사이즈의 배수 interpolation
보간법 - 사이즈를 줄일 때 :
!wget -O moon.jpg https://cdn.pixabay.com/photo/2020/05/26/20/38/moon-5224745_960_720.jpg
--2021-10-25 09:48:49-- https://cdn.pixabay.com/photo/2020/05/26/20/38/moon-5224745_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 46659 (46K) [image/jpeg]
Saving to: ‘moon.jpg’
moon.jpg 100%[===================>] 45.57K --.-KB/s in 0.01s
2021-10-25 09:48:49 (4.35 MB/s) - ‘moon.jpg’ saved [46659/46659]
img = cv2.imread('/content/moon.jpg')
print(img.shape)
(640, 960, 3)
cv2_imshow(img)
height, width = img.shape[:2]
shrink = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
zoom1 = cv2.resize(img, (width*2, height*2), interpolation=cv2.INTER_CUBIC)
zoom2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
cv2_imshow(shrink)
print(shrink.shape)
(320, 480, 3)
cv2_imshow(zoom1)
print(zoom1.shape)
(1280, 1920, 3)
cv2_imshow(zoom2)
print(zoom2.shape)
(1280, 1920, 3)
Translation
cv2.warpAffine()
- 이미지의 위치를 변경
파라미터 | 설명 |
---|---|
src |
Image |
M |
변환 행렬 |
dsize (tuple) |
output image size(e.g., (width=columns, height-rows) |
rows, cols = img.shape[:2]
M = np.float32([[1,0,20],[0,1,40]])
dst = cv2.warpAffine(img, M, (cols, rows))
cv2_imshow(dst)
Rotate
cv2.getRotationMatrix2D()
- 물체를 평면상의 한 점을 중심으로 θ 만큼 회전하는 변환
- 양의 각도는 시계 반대 방향으로 회전
- 음의 각도는 시계 방향으로 회전
파라미터 | 설명 |
---|---|
center |
이미지의 중심 좌표 |
angle |
회전 각도 |
scale |
scale factor |
rows, cols = img.shape[:2]
M = cv2.getRotationMatrix2D((cols/2, rows/2), 60, 0.5)
dst = cv2.warpAffine(img, M, (cols, rows))
cv2_imshow(dst)
print(dst.shape)
(640, 960, 3)
Flip
cv2.flip()
- 대칭 변환
- 좌우 대칭(좌우 반전)
- 상하 대칭(상하 반전)
- 입력 영상과 출력 영상의 필셀이 1:1 매칭이므로 보간법이 필요 없음
img = cv2.imread('/content/moon.jpg')
print(img.shape)
(640, 960, 3)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.show()
rs1 = cv2.flip(img, 1)
plt.imshow(rs1)
plt.show()
rs1 = cv2.flip(img, 0)
plt.imshow(rs1)
plt.show()
rs1 = cv2.flip(img, -1)
plt.imshow(rs1)
plt.show()
Affine Transformation
cv2.getAffineTransform()
- 선의 평행선은 유지되면서 이미지를 변환하는 작업
- 이동, 확대, scale, 반전까지 포함된 변환
- Affine 변환을 위해서는 3개의 Match가 되는 점이 있으면 변환 행렬을 구할 수 있음
rows, cols, ch = img.shape
pts1 = np.float32([[200,100], [400,100], [200, 200]])
pts2 = np.float32([[200,300], [400,200], [200, 400]])
cv2.circle(img, (200,100), 10, (255,0,0), -1)
cv2.circle(img, (400,100), 10, (0,255,0), -1)
cv2.circle(img, (200,200), 10, (0,0,255), -1)
M = cv2.getAffineTransform(pts1, pts2)
dst = cv2.warpAffine(img, M, (cols, rows))
cv2_imshow(dst)
print(dst.shape)
(640, 960, 3)
plt.subplot(121)
plt.imshow(img[:,:,::-1])
plt.title('Image')
plt.subplot(122)
plt.imshow(dst[:,:,::-1])
plt.title('Affine')
Text(0.5, 1.0, 'Affine')
Perspective Transformation
- 원근법(Perspective) 변환
- 직선의 성질만 유지, 선의 평행성은 유지가 되지 않는 변환
- 기차길은 서로 평행하지만 원근 변환을 거치면 평행성은 유지 되지 못하고 하나의 점에서 만나는 것처럼 보임(반대의 변환도 가능)
- 4개의 Point의 Input 값과 이동할 output point가 필요
cv2.getPerspectiveTransform()
가 필요하며,cv2.warpPerspective()
함수에 변환 행렬값을 적용하여 최종 결과 이미지를 얻을 수 있음
!wget -O train.jpg https://cdn.pixabay.com/photo/2015/04/04/06/54/train-706219_960_720.jpg
--2021-10-25 09:48:54-- https://cdn.pixabay.com/photo/2015/04/04/06/54/train-706219_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 104848 (102K) [image/jpeg]
Saving to: ‘train.jpg’
train.jpg 100%[===================>] 102.39K --.-KB/s in 0.02s
2021-10-25 09:48:54 (4.80 MB/s) - ‘train.jpg’ saved [104848/104848]
img = cv2.imread('/content/train.jpg')
print(img.shape)
(720, 481, 3)
cv2_imshow(img)
- 좌표점은 (왼쪽 위) -> (오른쪽 위) -> (오른쪽 아래) -> (왼쪽 아래)
top_left = (180, 300)
top_right = (270, 300)
bottom_left = (80, 550)
bottom_right = (400, 550)
pts1 = np.float32([top_left, top_right, bottom_right, bottom_left])
w1 = abs(bottom_right[0] - bottom_left[0])
w2 = abs(top_right[0] - top_left[0])
h1 = abs(top_right[1] - bottom_right[1])
h2 = abs(top_left[1] - bottom_left[1])
max_width = max([w1, w2])
max_height = max([h1, h2])
pts2 = np.float32([[0,0], [max_width-1,0], [max_width-1,max_height-1], [0, max_height-1]])
cv2.circle(img, top_left, 10, (255,0,0), -1)
cv2.circle(img, top_right, 10, (0,255,0), -1)
cv2.circle(img, bottom_left, 10, (0,0,255), -1)
cv2.circle(img, bottom_right, 10, (255,255,255), -1)
cv2_imshow(img)
M = cv2.getPerspectiveTransform(pts1, pts2)
dst = cv2.warpPerspective(img, M, (max_width, max_height))
plt.subplot(121)
plt.imshow(img[:,:,::-1])
plt.title('Image')
plt.subplot(122)
plt.imshow(dst[:,:,::-1])
plt.title('Perspective')
plt. show()
이미지 연산(Image Operation)
- 이미지는 배열(array)로 표현 가능하여 여러가지 연산 가능
!wget -O tree.jpg https://cdn.pixabay.com/photo/2014/03/05/21/12/desert-279862_960_720.jpg
--2021-10-25 09:48:55-- https://cdn.pixabay.com/photo/2014/03/05/21/12/desert-279862_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 164397 (161K) [image/jpeg]
Saving to: ‘tree.jpg’
tree.jpg 100%[===================>] 160.54K --.-KB/s in 0.03s
2021-10-25 09:48:55 (6.15 MB/s) - ‘tree.jpg’ saved [164397/164397]
tree = cv2.imread('/content/tree.jpg')
print(tree.shape)
(654, 960, 3)
cv2_imshow(tree)
temp_px = tree[200,200]
print(temp_px)
[50 49 39]
temp_ch = tree[200,200,2]
print(temp_ch)
39
값 변경
tree[100:120,100] = [0,0,255]
cv2_imshow(tree)
인덱싱
print(tree[:100,:100].shape)
(100, 100, 3)
cv2_imshow(tree[400:500,400:500])
white_box = tree[400:500,400:500]
white_box = [255,255,255]
tree[400:500,400:500] = white_box
cv2_imshow(tree)
이미지 ROI
- 이미지 작업시에는 특정 pixel 단위 보다는 특정 영역단위로 작업을 하게 되는데 이것을 Region of Image(ROI)라고 함
- ROI 설정은 Numpy의 indexing을 사용, 특정 영역을 copy 할 수도 있음
tree = cv2.imread('/content/tree.jpg')
print(tree.shape)
(654, 960, 3)
cv2_imshow(tree)
t = tree[:500,760:]
cv2_imshow(t)
tree[:500,100:300] = t
cv2_imshow(tree)
tree[100:600,400:600] = t
cv2_imshow(tree)
이미지의 Channels
- B, G, R로 구성된 채널을 분리, 합칠 수 있음
cv2.split()
cv2.merge()
b, g, r = cv2.split(tree)
print(b)
print(b.shape)
[[ 32 32 33 ... 55 55 55]
[ 32 32 32 ... 55 55 55]
[ 32 32 32 ... 54 54 54]
...
[ 42 47 31 ... 127 132 109]
[ 26 9 1 ... 96 117 140]
[ 16 65 107 ... 131 116 110]]
(654, 960)
print(g)
print(g.shape)
[[ 30 30 31 ... 55 55 55]
[ 30 30 30 ... 55 55 55]
[ 30 30 30 ... 54 54 54]
...
[ 43 49 34 ... 150 155 132]
[ 30 13 7 ... 123 144 167]
[ 22 71 116 ... 158 144 138]]
(654, 960)
print(r)
print(r.shape)
[[ 20 20 21 ... 43 43 43]
[ 20 20 20 ... 43 43 43]
[ 20 20 20 ... 42 42 42]
...
[ 53 57 42 ... 176 181 158]
[ 41 24 18 ... 144 164 187]
[ 35 84 126 ... 178 161 155]]
(654, 960)
img = cv2.merge((b,g,r))
print(img.shape)
(654, 960, 3)
cv2_imshow(img)
cv2.split()
함수는 비용이 많이 드는 함수이므로, 가능하다면 Numpy indexing을 사용하는게 효율적- R 채널 0으로 값 변경
img[:,:,2] = 0
print(img)
[[[ 32 30 0]
[ 32 30 0]
[ 33 31 0]
...
[ 55 55 0]
[ 55 55 0]
[ 55 55 0]]
[[ 32 30 0]
[ 32 30 0]
[ 32 30 0]
...
[ 55 55 0]
[ 55 55 0]
[ 55 55 0]]
[[ 32 30 0]
[ 32 30 0]
[ 32 30 0]
...
[ 54 54 0]
[ 54 54 0]
[ 54 54 0]]
...
[[ 42 43 0]
[ 47 49 0]
[ 31 34 0]
...
[127 150 0]
[132 155 0]
[109 132 0]]
[[ 26 30 0]
[ 9 13 0]
[ 1 7 0]
...
[ 96 123 0]
[117 144 0]
[140 167 0]]
[[ 16 22 0]
[ 65 71 0]
[107 116 0]
...
[131 158 0]
[116 144 0]
[110 138 0]]]
cv2_imshow(img)
- G 채널 0으로 값 변경
img[:,:,1] = 0
print(img)
[[[ 32 0 0]
[ 32 0 0]
[ 33 0 0]
...
[ 55 0 0]
[ 55 0 0]
[ 55 0 0]]
[[ 32 0 0]
[ 32 0 0]
[ 32 0 0]
...
[ 55 0 0]
[ 55 0 0]
[ 55 0 0]]
[[ 32 0 0]
[ 32 0 0]
[ 32 0 0]
...
[ 54 0 0]
[ 54 0 0]
[ 54 0 0]]
...
[[ 42 0 0]
[ 47 0 0]
[ 31 0 0]
...
[127 0 0]
[132 0 0]
[109 0 0]]
[[ 26 0 0]
[ 9 0 0]
[ 1 0 0]
...
[ 96 0 0]
[117 0 0]
[140 0 0]]
[[ 16 0 0]
[ 65 0 0]
[107 0 0]
...
[131 0 0]
[116 0 0]
[110 0 0]]]
cv2_imshow(img)
- B 채널 0으로 값 변경
img[:,:,0] = 0
print(img)
[[[0 0 0]
[0 0 0]
[0 0 0]
...
[0 0 0]
[0 0 0]
[0 0 0]]
[[0 0 0]
[0 0 0]
[0 0 0]
...
[0 0 0]
[0 0 0]
[0 0 0]]
[[0 0 0]
[0 0 0]
[0 0 0]
...
[0 0 0]
[0 0 0]
[0 0 0]]
...
[[0 0 0]
[0 0 0]
[0 0 0]
...
[0 0 0]
[0 0 0]
[0 0 0]]
[[0 0 0]
[0 0 0]
[0 0 0]
...
[0 0 0]
[0 0 0]
[0 0 0]]
[[0 0 0]
[0 0 0]
[0 0 0]
...
[0 0 0]
[0 0 0]
[0 0 0]]]
cv2_imshow(img)
이미지 더하기
cv2.add()
cv2.addWeighted()
Numpy
더하기 연산cv2.add()
: Saturation 연산Saturation
연산은 한계값을 정하고 그 값을 벗어나는 경우는 모두 특정 값으로 계산하는 방식- 이미지에서는 0 이하는 모두 0, 255 이상은 모두 255로 표현
Numpy
: modulo 연산- a와 b는 n으로 나눈 나머지 값이 같다라는 의미
- 이미지에서는 연산의 결과가 256 보다 큰 경우는 256으로 나눈 나머지 값으로 결정
x = np.uint8([250])
y = np.uint8([10])
cv2.add()
연산
cv2.add(x,y)
array([[255]], dtype=uint8)
Numpy
연산
print(x+y)
[4]
!wget -O dog1.jpg https://cdn.pixabay.com/photo/2021/01/26/17/18/cavalier-king-charles-spaniel-5952324_960_720.jpg
!wget -O dog2.jpg https://cdn.pixabay.com/photo/2017/09/25/13/14/dog-2785077_960_720.jpg
dog1 = cv2.imread('/content/dog1.jpg')
dog2 = cv2.imread('/content/dog2.jpg')
print(dog1.shape)
print(dog2.shape)
--2021-10-25 09:48:59-- https://cdn.pixabay.com/photo/2021/01/26/17/18/cavalier-king-charles-spaniel-5952324_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 96413 (94K) [image/jpeg]
Saving to: ‘dog1.jpg’
dog1.jpg 100%[===================>] 94.15K --.-KB/s in 0.01s
2021-10-25 09:48:59 (6.39 MB/s) - ‘dog1.jpg’ saved [96413/96413]
--2021-10-25 09:48:59-- https://cdn.pixabay.com/photo/2017/09/25/13/14/dog-2785077_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 110491 (108K) [image/jpeg]
Saving to: ‘dog2.jpg’
dog2.jpg 100%[===================>] 107.90K --.-KB/s in 0.02s
2021-10-25 09:48:59 (4.85 MB/s) - ‘dog2.jpg’ saved [110491/110491]
(640, 960, 3)
(640, 960, 3)
cv2.add()
연산
rs1 = cv2.add(dog1,dog2)
cv2_imshow(rs1)
Numpy
연산
rs2 = dog1 + dog2
cv2_imshow(rs2)
비트 연산
- AND, OR, NOT, XOR 연산
bitwise_and
: 둘 다 0이 아닌 경우만 값을 통과bitwise_or
: 둘 중 하나가 0이 아니면 값을 통과bitwise_not
: 해당 값에 대해 부정값을 통과bitwise_xor
: 두 요소의 논리적 배타값 통과
!wget -O star.png https://www.pinclipart.com/picdir/middle/124-1240560_star-vector-art-9-buy-clip-art-compass.png
--2021-10-25 09:48:59-- https://www.pinclipart.com/picdir/middle/124-1240560_star-vector-art-9-buy-clip-art-compass.png
Resolving www.pinclipart.com (www.pinclipart.com)... 173.208.239.244
Connecting to www.pinclipart.com (www.pinclipart.com)|173.208.239.244|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 142551 (139K) [image/png]
Saving to: ‘star.png’
star.png 100%[===================>] 139.21K --.-KB/s in 0.08s
2021-10-25 09:49:00 (1.78 MB/s) - ‘star.png’ saved [142551/142551]
star = cv2.imread('/content/star.png')
print(star.shape)
(800, 880, 3)
plt.imshow(star)
plt.show()
bk_img = np.zeros((800,880,3))
plt.imshow(bk_img)
plt.show()
bk_img[:,:440,:] = (255,255,255)
- 이미지의 dtype을 int형으로 변환
img = bk_img.astype(np.uint8)
plt.imshow(img)
plt.show()
bitwise_and
연산
rs = cv2.bitwise_and(img, star)
plt.imshow(rs)
plt.show()
bitwise_or
연산
rs = cv2.bitwise_or(img, star)
plt.imshow(rs)
plt.show()
bitwise_not
연산
rs = cv2.bitwise_not(img)
plt.imshow(rs)
plt.show()
rs = cv2.bitwise_not(star)
plt.imshow(rs)
plt.show()
bitwise_xor
연산
rs = cv2.bitwise_xor(img, star)
plt.imshow(rs)
plt.show()
tmp_star = cv2.bitwise_not(star)
rs = cv2.bitwise_xor(img, tmp_star)
plt.imshow(rs)
plt.show()
비트 연산 예시
- 이미지에 OpenCV 로고 넣기
!wget -O bk.jpg https://cdn.pixabay.com/photo/2011/12/14/12/17/galaxy-11098_960_720.jpg
!wget -O logo.png https://media.vlpt.us/images/seonghun-dev/post/72db176a-a9e5-492f-a484-7024388098cc/opencv_logo_icon_170888.png
--2021-10-25 09:49:03-- https://cdn.pixabay.com/photo/2011/12/14/12/17/galaxy-11098_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 61119 (60K) [image/jpeg]
Saving to: ‘bk.jpg’
bk.jpg 100%[===================>] 59.69K --.-KB/s in 0.01s
2021-10-25 09:49:03 (5.44 MB/s) - ‘bk.jpg’ saved [61119/61119]
--2021-10-25 09:49:03-- https://media.vlpt.us/images/seonghun-dev/post/72db176a-a9e5-492f-a484-7024388098cc/opencv_logo_icon_170888.png
Resolving media.vlpt.us (media.vlpt.us)... 172.67.157.210, 104.21.8.194, 2606:4700:3030::6815:8c2, ...
Connecting to media.vlpt.us (media.vlpt.us)|172.67.157.210|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 15467 (15K) [image/png]
Saving to: ‘logo.png’
logo.png 100%[===================>] 15.10K --.-KB/s in 0s
2021-10-25 09:49:03 (61.2 MB/s) - ‘logo.png’ saved [15467/15467]
bk = cv2.imread('/content/bk.jpg')
logo = cv2.imread('/content/logo.png')
print(bk.shape)
print(logo.shape)
(600, 960, 3)
(407, 749, 3)
- 삽입할 이미지의 row, cols, channel 정보
- 대상 이미지에서 삽입할 이미지의 영역을 추출
rows, cols, ch = logo.shape
bk_img = bk[0:rows,0:cols]
bk_img.shape
(407, 749, 3)
rows, cols, ch = bk_img.shape
opencv = logo[0:rows,0:cols]
opencv.shape
(407, 749, 3)
- mask를 만들기 위해서 logo 이미지를 gray scale로 변경 후 binary image로 전환
- mask 는 logo 부분이 흰색(255), 바탕은 검은색(0)
- mask_inv는 log 부분이 검은색(0), 바탕은 흰색(255)
opencv_gray = cv2.cvtColor(opencv, cv2.COLOR_BGR2GRAY)
plt.imshow(opencv_gray, cmap='gray')
plt.show()
ret, mask = cv2.threshold(opencv_gray,200,255,cv2.THRESH_BINARY_INV)
mask_inv = cv2.bitwise_not(mask)
plt.imshow(mask, cmap='gray')
plt.show()
plt.imshow(mask_inv, cmap='gray')
plt.show()
img1_fg = cv2.bitwise_and(opencv, opencv, mask=mask)
img2_bg = cv2.bitwise_and(bk_img, bk_img, mask=mask_inv)
dst = cv2.add(img1_fg,img2_bg)
cv2_imshow(dst)
bk_img[:rows,:cols] = dst
cv2_imshow(bk_img)
이미지 블렌딩(Image Blending)
cv2.addWeighted()
- 두 이미지를 blending 할 수 있음
- blending 하려는 두 이미지의 사이즈가 같아야함
- [Simple Formula]
\(g(x) = (1-α)f_0(x_I) + αf_1(x)\)
- β = 1 - α
- α, β의 값을 통해 어떤 이미지를 더 강하게 드러내고, 어떤 이미지를 더 약하게 드러낼지 결정
- γ 추가 가능 (optional)
!wget -O dnc.jpg https://thumbs.dreamstime.com/b/red-do-not-copy-stencil-type-word-watermark-frame-103747612.jpg
!wget -O bk.jpg https://cdn.pixabay.com/photo/2016/11/19/10/29/background-1838494_960_720.jpg
--2021-10-25 09:49:04-- https://thumbs.dreamstime.com/b/red-do-not-copy-stencil-type-word-watermark-frame-103747612.jpg
Resolving thumbs.dreamstime.com (thumbs.dreamstime.com)... 192.229.163.122
Connecting to thumbs.dreamstime.com (thumbs.dreamstime.com)|192.229.163.122|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 35192 (34K) [image/jpeg]
Saving to: ‘dnc.jpg’
dnc.jpg 100%[===================>] 34.37K --.-KB/s in 0s
2021-10-25 09:49:04 (108 MB/s) - ‘dnc.jpg’ saved [35192/35192]
--2021-10-25 09:49:04-- https://cdn.pixabay.com/photo/2016/11/19/10/29/background-1838494_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 262746 (257K) [image/jpeg]
Saving to: ‘bk.jpg’
bk.jpg 100%[===================>] 256.59K --.-KB/s in 0.03s
2021-10-25 09:49:04 (7.63 MB/s) - ‘bk.jpg’ saved [262746/262746]
img1 = cv2.imread('/content/bk.jpg')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
img2 = cv2.imread('/content/dnc.jpg')
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
print(img1.shape)
print(img2.shape)
(640, 960, 3)
(571, 800, 3)
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
img1 = cv2.resize(img1, (500,500))
img2 = cv2.resize(img2, (500,500))
print(img1.shape)
print(img2.shape)
(500, 500, 3)
(500, 500, 3)
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
blended = cv2.addWeighted(src1=img1, alpha=0.5, src2=img2, beta=0.5, gamma=0)
plt.imshow(blended)
plt.show()
blended = cv2.addWeighted(src1=img1, alpha=0.8, src2=img2, beta=0.1, gamma=0)
plt.imshow(blended)
plt.show()
이미지 이진화(Image Theshoding)
기본 임계 처리
cv2.threshold()
- 이진화 : 영상을 흑/백으로 분류하여 처리하는 것
- 기준이 되는 임계값을 어떻게 결정할 것인지가 중요한 문제
- 임계값보다 크면 백, 작으면 흑이 되는데, 기본 임계처리는 사용자가 고정된 임계값을 결정하고 그 결과를 보여주는 단순한 형태
- 기본적으로 이미지의 segmenting의 가장 간단한 방법
파라미터 설명 src
Input Image로 single-channel 이미지.(grayscale 이미지) thresh
임계값 maxval
임계값을 넘었을 때 적용함 value type
thresholding type - thresholding type
cv2.THRESH_BINARY
- src(x,y) > thresh 일 때, maxval
- 그 외, 0
cv2.THRESH_BINARY_INV
- src(x,y) > thresh 일 때, 0
- 그 외, maxval
cv2.THRESH_TRUNC
- src(x,y) > thresh 일 때, thresh
- 그 외, src(x,y)
cv2.THRESH_TOZERO
- src(x,y) > thresh 일 때, src(x,y)
- 그 외, 0
cv2.THRESH_TOZERO_INV
- src(x,y) > thresh 일 때, 0
- 그 외, src(x,y)
!wget -O letters.jpg https://cdn.pixabay.com/photo/2020/12/09/17/18/letters-5818033_960_720.jpg
--2021-10-25 09:49:06-- https://cdn.pixabay.com/photo/2020/12/09/17/18/letters-5818033_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 241463 (236K) [image/jpeg]
Saving to: ‘letters.jpg’
letters.jpg 100%[===================>] 235.80K --.-KB/s in 0.03s
2021-10-25 09:49:06 (7.65 MB/s) - ‘letters.jpg’ saved [241463/241463]
img = cv2.imread('/content/letters.jpg', 0)
img.shape
(640, 960)
plt.imshow(img, cmap='gray')
plt.show()
ret, thresh1 = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, 128, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, 128, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, 128, 255, cv2.THRESH_TOZERO_INV)
titles = ['Original', 'Binary', 'Binary_Inv', 'Trunc', 'Tozero', 'Tozero_Inv']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
plt.figure(figsize=(14,8))
for i in range(6):
plt.subplot(2,3,i+1)
plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
!wget -O snow.jpg https://cdn.pixabay.com/photo/2016/03/09/09/21/snowflake-1245748_960_720.jpg
--2021-10-25 09:49:07-- https://cdn.pixabay.com/photo/2016/03/09/09/21/snowflake-1245748_960_720.jpg
Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:15b7, ...
Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 90437 (88K) [image/jpeg]
Saving to: ‘snow.jpg’
snow.jpg 100%[===================>] 88.32K --.-KB/s in 0.01s
2021-10-25 09:49:08 (5.85 MB/s) - ‘snow.jpg’ saved [90437/90437]
img = cv2.imread('/content/snow.jpg', 0)
img.shape
(720, 960)
plt.imshow(img, cmap='gray')
plt.show()
ret, thresh1 = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, 128, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, 128, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, 128, 255, cv2.THRESH_TOZERO_INV)
titles = ['Original', 'Binary', 'Binary_Inv', 'Trunc', 'Tozero', 'Tozero_Inv']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
plt.figure(figsize=(14,8))
for i in range(6):
plt.subplot(2,3,i+1)
plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
적응 임계처리
cv2.adaptiveThreshold()
- 이전 단계에서는 임계값을 이미지 전체에 적용하여 처리하기 때문에 하나의 이미지에 음영이 다르면 일부 영역이 모두 흰색 또는 검정색으로 보여지게 됨
- 이런 문제를 해결하기 위해서 이미지의 작은 영역별로 thresholding
파라미터 | 설명 |
---|---|
src |
grayscale image |
maxValue |
임계값 |
adaptiveMethod |
thresholding value를 결정하는 계산 방법 |
thresholdType |
threshold type |
blockSize |
thresholding을 적용할 영역 사이즈 |
C |
평균이나 가중평균에서 차감할 값 |
- Adaptive Method
cv2.ADAPTIVE_THRESH_MEAN_C
: 주변 영역의 평균값으로 결정cv2.ADAPTIVE_THRESH_GAUSSIAN_C
: 주변 영역의 가우시안 값으로 결정
img = cv2.imread('/content/letters.jpg', 0)
img.shape
(640, 960)
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 2)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 2)
titles = ['Original', 'Global', 'Mean', 'Gaussian']
images = [img, th1, th2, th3]
plt.figure(figsize=(14,10))
for i in range(4):
plt.subplot(2,2,i+1)
plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
Otsu의 이진화
Otsu의 이진화
(Otsu’s Binarization)란 bimodal image에서 임계값을 자동으로 계산하는 것- 임계값을 결정하는 가장 일반적인 방법은 trial and error 방식
- bimodal image(히스토그램으로 분석하면 2개의 peak가 있는 이미지)의 경우는 히스토그램에서 임계값을 어느 정도 정확히 계산 가능
cv2.threshold()
함수의 flag에 추가로cv2.THRESH_OTSU
를 적용. 이때 임계값은 0으로 전달
!wget -O noise.jpg https://i.stack.imgur.com/J13Wn.jpg
--2021-10-25 09:49:10-- https://i.stack.imgur.com/J13Wn.jpg
Resolving i.stack.imgur.com (i.stack.imgur.com)... 199.232.64.193
Connecting to i.stack.imgur.com (i.stack.imgur.com)|199.232.64.193|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 444265 (434K) [image/jpeg]
Saving to: ‘noise.jpg’
noise.jpg 100%[===================>] 433.85K --.-KB/s in 0.04s
2021-10-25 09:49:10 (10.5 MB/s) - ‘noise.jpg’ saved [444265/444265]
noise = cv2.imread('/content/noise.jpg', 0)
noise.shape
(720, 960)
ret1, th1 = cv2.threshold(noise, 127, 255, cv2.THRESH_BINARY)
ret2, th2 = cv2.threshold(noise, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
blur = cv2.GaussianBlur(noise, (5,5), 0)
ret3, th3 = cv2.threshold(noise, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
titles = ['Original', 'Histogram', 'Global Thresholding (t=127)',
'Original', 'Histogram', 'Otsu Thresholding',
'Gaussian', 'Histogram', 'Otsu Thresholding']
images = [noise, 0, th1, noise, 0, th2, blur, 0, th3]
plt.figure(figsize=(14,10))
for i in range(3):
plt.subplot(3,3,i*3+1)
plt.imshow(images[i*3], 'gray')
plt.title(titles[i*3])
plt.xticks([])
plt.yticks([])
plt.subplot(3,3,i*3+2)
plt.hist(images[i*3].ravel(), 256)
plt.title(titles[i*3+1])
plt.xticks([])
plt.yticks([])
plt.subplot(3,3,i*3+3)
plt.imshow(images[i*3+2], 'gray')
plt.title(titles[i*3+2])
plt.xticks([])
plt.yticks([])
plt.show()
이미지 필터링(Image Filtering)
cv2.filter2D()
- 이미지도 음성 신호처럼 주파수로 표현할 수 있음
- 일반적으로 고주파는 밝기의 변화가 많은 곳, 즉 경계선 영역에서 나타나며, 일반적인 배경은 저주파로 나타냄
- 이것을 바탕으로 고주파를 제거하면 Blur 처리가 되며, 저주파를 제거하면 대상의 영역을 확인 가능
- Low-pass filter(LPF)와 High-pass filter(HPF)를 이용하여, LPF를 적용하면 노이즈제거나 blur 처리를 할 수 있으며, HPF를 적용하면 경계선을 찾을 수 있음
- 일반적으로 많이 사용되는 필터
- ex) \(K = \frac{1}{25}\begin{vmatrix}1&1&1&1&1\\1&1&1&1&1\\1&1&1&1&1\\1&1&1&1&1\\1&1&1&1&1\\ \end{vmatrix}\)
!wget -O bee.jpg https://ideas.ted.com/wp-content/uploads/sites/3/2021/03/FINAL_Bees.jpg?resize=750,450
--2021-10-25 09:49:12-- https://ideas.ted.com/wp-content/uploads/sites/3/2021/03/FINAL_Bees.jpg?resize=750,450
Resolving ideas.ted.com (ideas.ted.com)... 192.0.66.192, 2a04:fa87:fffd::c000:42c0
Connecting to ideas.ted.com (ideas.ted.com)|192.0.66.192|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 260164 (254K) [image/jpeg]
Saving to: ‘bee.jpg’
bee.jpg 100%[===================>] 254.07K --.-KB/s in 0.04s
2021-10-25 09:49:13 (5.59 MB/s) - ‘bee.jpg’ saved [260164/260164]
img = cv2.imread('/content/bee.jpg')
img.shape
(450, 750, 3)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.show()
kernel = np.ones((5,5), np.float32) / 25
print(kernel.shape)
print(kernel)
(5, 5)
[[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]]
dst = cv2.filter2D(img, -1, kernel)
plt.imshow(dst)
plt.show()
이미지 샤프닝(Image Sharpening)
- 출력화소에서 이웃 화소끼리 차이를 크게 해서 날카로운 느낌이 나게 만드는 것
- 영상의 세세한 부분을 강조할 수 있으며, 경계 부분에서 명암대비가 증가되는 효과
- 사프닝 커널
- 커널 원소들의 값 차이가 커지도록 구성
- 커널 원소 전체 합이 1이 되어야 입력영상 밝기가 손실 없이 출력 영상 밝기로 유지
img = cv2.imread('/content/bee.jpg')
img.shape
(450, 750, 3)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.show()
kernel = [0, -1, 0,
-1, 5, -1,
0, -1, 0]
mask = np.array(kernel, np.float32).reshape(3,3)
sharpen = cv2.filter2D(img, -1, mask)
plt.imshow(sharpen)
plt.show()
kernel = [-1, -1, -1,
-1, 9, -1,
-1, -1, -1]
mask = np.array(kernel, np.float32).reshape(3,3)
sharpen = cv2.filter2D(img, -1, mask)
plt.imshow(sharpen)
plt.show()
이미지 블러링(Image Blurring)
- low-pass filter를 이미지에 적용하여 얻을 수 있음
- 고주파영역을 제거함으로써 노이즈를 제거하거나 경계선을 흐리게 할 수 있음
- OpenCV에서 저공하는 blurring 방법
Averaging
Gaussian Filtering
Median Filtering
Bilateral Filtering
Averaging
- Box형태의 kernel을 이미지에 적용한 후 평균값을 box의 중심점에 적용하는 형태
cv2.blur()
또는cv2.boxFilter()
cv2.blur()
- Parameters
src
: Chennel 수는 상관없으나, depth(Data Type)은 CV_8U, CV_16U, CV_16S, CV_32F or CV_64Fksize
: kernel 사이즈(ex; (3,3))
- ex) \(K = \frac{1}{9}\begin{vmatrix}1&1&1\\1&1&1\\1&1&1\\ \end{vmatrix}\)
- 이미지의 Data Type
데이터 타입 설명 CV_8U
8-bit unsigned integer: uchar (0..255) CV_8S
8-bit signed integer: schar (-128..127) CV_16U
16-bit unsigned integer: ushort (0..65535) CV_16S
16-bit signed integer: short (-32768..32767) CV_32S
32-bit signed integer: int (-2147483648..2147483647) CV_32F
32-bit floating-point number: float (-FLT_MAX..FLT_MAX, INF, NAN) CV_64F
64-bit floating-point number: double (-DBL_MAX..DBL_MAX, INF, NAN) - 일반적으로 Data Type과 채널수가 같이 표현이 되어 CV_8UC1과 같이 표현(8bit unsigned integer이면서 채널이 1개)
- Parameters
img = cv2.imread('/content/bee.jpg')
img.shape
(450, 750, 3)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.title('Original')
plt.imshow(img)
plt.show()
dst1 = cv2.blur(img, (7,7))
plt.title('Averaging Blurring')
plt.imshow(dst1)
plt.show()
Gaussian Filtering
cv2.GaussianBlur()
- box filter는 동일한 값으로 구성된 kernel을 사용하지만, Gaussian Filter는 Gaussian 함수를 이용한 kernel을 적용
- kernel 행렬의 값을 Gaussian 함수를 통해서 수학적으로 생성하여 적용
- kernel의 사이즈는 야수이면서 홀수로 지정을 해야 됨
- 이미지의 Gaussian Noise(전체적으로 밀도가 동일한 노이즈, 백색 노이즈)를 제거하는데 가장 효과적
파라미터 | 설명 |
---|---|
img |
Chennel수는 상관없으나, depth(Data Type)은 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F |
ksize |
(width, height) 형태의 kernel size, width와 height는 서로 다를 수 있지만, 양수의 홀수로 지정해야함 |
sigmaX |
Gaussian kernel standard deviation in X direction |
plt.title('Original')
plt.imshow(img)
plt.show()
dst2 = cv2.GaussianBlur(img, (5,5), 0)
plt.title('GaussianBlur')
plt.imshow(dst2)
plt.show()
Median Filtering
cv2.medianBlur()
- kernel window와 pixel의 값들을 정렬한 후에 중간값을 선택하여 적용
- salt-and-pepper noise 제거에 가장 효과적
파라미터 | 설명 |
---|---|
src |
1,3,4 channel image, depth가 CV_8U, CV_16U, or CV_32_F 이면 ksize는 3 또는 5, CV_8U이면 더 큰 ksize 가능 |
ksize |
1보다 큰 홀수 |
plt.title('Original')
plt.imshow(img)
plt.show()
dst3 = cv2.medianBlur(img, 9)
plt.title('Median Blur')
plt.imshow(dst3)
plt.show()
Bilateral Filtering
cv2.bilateralFilter()
- 위 3가지 Blur 방식은 경계선까지 blur 처리가 되어, 경계선이 흐려짐
- Bilateral Filtering(양방향 필터)은 경계선을 유지하면서 Gaussian Blur 처리를 해주는 방법
파라미터 | 설명 |
---|---|
src |
8-bit, 1 or 3 Channel image |
d |
filtering 시 고려할 주변 pixel 지름 |
sigmaColor |
Color를 고려할 공간. 숫자가 크면 멀리 있는 색도 고려 |
sigmaSpace |
숫자가 크면 멀리 있는 pixel도 고려 |
plt.title('Original')
plt.imshow(img)
plt.show()
dst4 = cv2.bilateralFilter(img, 9, 75, 75)
plt.title('Bilateral Filter')
plt.imshow(dst4)
plt.show()
titles = ['Original', 'Blur(7x7)', 'Gaussian Blur(5x5)', 'Median Blur', 'Bilateral']
images = [img, dst1, dst2, dst3, dst4]
plt.figure(figsize=(14,16))
for i in range(5):
plt.subplot(3,2,i+1)
plt.imshow(images[i])
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
형태학적 변환(Morphological Transformations)
- 이미지를 Segmentation하여 단순화, 제거, 보정을 통해서 형태를 파악하는 목적으로 사용
- 일반적으로 binary나 grayscale image에 사용
- 사용하는 방법으로는 Dilation(팽창), Erosion(침식), 그리고 2개를 조합한 Opening과 Closing이 있음
- 여기에는 2가지 Input 값이 있는데, 하나는 원본 이미지이고 또 다른 하나는 structuring element
- structuring element
- 원본 이미지에 적용되는 kernel
- 중심을 원점으로 사용할 수도 있고, 원점을 변경할 수도 있음
- 일반적으로 꽉찬 사각형, 타원형, 십자가형을 많이 사용
!wget -O two.jpg https://hollandbikeshop.com/img/prod/gmg-yepp-abc-susja-2-8715362005809-0-l.jpg
--2021-10-25 09:49:18-- https://hollandbikeshop.com/img/prod/gmg-yepp-abc-susja-2-8715362005809-0-l.jpg
Resolving hollandbikeshop.com (hollandbikeshop.com)... 213.206.238.157, 2001:4018:8800:100:213:206:238:157
Connecting to hollandbikeshop.com (hollandbikeshop.com)|213.206.238.157|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [image/jpeg]
Saving to: ‘two.jpg’
two.jpg [ <=> ] 19.02K --.-KB/s in 0.09s
2021-10-25 09:49:19 (203 KB/s) - ‘two.jpg’ saved [19478]
img = cv2.imread('/content/two.jpg')
cv2_imshow(img)
Erosion
cv2.erode()
- 각 Pixel에 structuring element를 적용하여 하나라도 0이 있으면 대상 pixel을 제거하는 방법
- 작은 object를 제거하는 효과
파라미터 | 설명 |
---|---|
src |
the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F of CV_64F |
kernel |
structuring element. cv2.getStructuringElemet() 함수로 만들 수 있음 |
anchor |
structuring element의 중심. default(-1,-1)로 중심점 |
iterations |
erosion 적용 반복 횟수 |
- 아래 그림은 대상 이미지에 십자형 structuring element를 적용한 결과
kernel = np.ones((5,5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
cv2_imshow(erosion)
Dilation
cv2.dilate()
- Erosion과 반대 작용
- 대상을 확장한 후 작은 구멍을 채우는 방법
- Erosion과 마찬가지로 각 pixel에 structuring element를 적용
- 대상 pixel에 대해서 OR 연산을 수행
- 즉, 겹치는 부분이 하나라도 있으면 이미지를 확장
kernel = np.ones((5,5), np.uint8)
dilation = cv2.dilate(img, kernel, iterations=1)
cv2_imshow(dilation)
Opening & Closing
cv2.morphologyEx()
- Opening과 Closing은 Erosion 과 Dilation의 조합 결과
- 차이는 어느 것을 먼저 적용을 하는 차이
Opening
: Erosion 적용 후 Dilation 적용. 작은 Object이나 돌기 제거에 적합Closing
: Dilation 적용 후 Erosion 적용. 전체적인 윤곽 파악에 적합
파라미터 | 설명 |
---|---|
src |
원본 이미지. 채널수는 상관 없으나, depth는 다음 중 하나여야함(CV_8U , CV_16U , CV_16S , CV_32F , CV_64F |
op |
연산 방법 |
MORPH_OPEN |
열기 동작 |
MORPH_COLOSE |
닫기 동작 |
MORPH_GRADIENT |
a morphological gradient. Diation과 Erosion의 차이 |
MORPH_TOPHAT |
“top hat”, Opening과 원본 이미지의 차이 |
MORPH_BLACKHAT |
“black hat”, Closing과 원본 이미지의 차이 |
kernel |
structuring element. cv2.getStructuringElemet() 함수로 만들 수 있음 |
anchor |
structuring element의 중심. default(-1,-1)로 중심점 |
iterations |
erosion과 dilation 적용 횟수 |
borderType |
픽셀 외삽법 borderInterpolate 참고(https://github.com/MagnusBai/notes/wiki/opencv-borderType-pixel-extrapolation-method) |
borderValue |
테두리 값 |
!wget -O opening.png https://docs.opencv.org/4.5.2/opening.png
!wget -O closing.png https://docs.opencv.org/4.5.2/closing.png
--2021-10-25 09:49:19-- https://docs.opencv.org/4.5.2/opening.png
Resolving docs.opencv.org (docs.opencv.org)... 172.67.218.21, 104.21.24.86, 2606:4700:3034::ac43:da15, ...
Connecting to docs.opencv.org (docs.opencv.org)|172.67.218.21|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2082 (2.0K) [image/png]
Saving to: ‘opening.png’
opening.png 100%[===================>] 2.03K --.-KB/s in 0s
2021-10-25 09:49:19 (27.1 MB/s) - ‘opening.png’ saved [2082/2082]
--2021-10-25 09:49:19-- https://docs.opencv.org/4.5.2/closing.png
Resolving docs.opencv.org (docs.opencv.org)... 172.67.218.21, 104.21.24.86, 2606:4700:3034::ac43:da15, ...
Connecting to docs.opencv.org (docs.opencv.org)|172.67.218.21|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2205 (2.2K) [image/png]
Saving to: ‘closing.png’
closing.png 100%[===================>] 2.15K --.-KB/s in 0s
2021-10-25 09:49:19 (20.8 MB/s) - ‘closing.png’ saved [2205/2205]
opening = cv2.imread('/content/opening.png')
cv2_imshow(opening)
opening = cv2.morphologyEx(opening, cv2.MORPH_OPEN, kernel)
cv2_imshow(opening)
closing = cv2.imread('/content/closing.png')
cv2_imshow(closing)
closing = cv2.morphologyEx(closing, cv2.MORPH_CLOSE, kernel)
cv2_imshow(closing)
Morphological Gradient
- dilation과 erosion의 차이
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv2_imshow(gradient)
Top Hat
- 입력 이미지와 opening 이미지와의 차이
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2_imshow(tophat)
Black Hat
- 입력 이미지와 closing 이미지와의 차이
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2_imshow(blackhat)
Structuring Element
- 사각형 모양의 structuring element는 numpy를 통해 만들 수 있음
kernel = np.ones((5,5), np.uint8)
kernel
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
- 원이나 타원 모양이 필요한 경우,
cv2.getStructuringElement()
이용- Parameters
shape
: Element의 모양.MORPH_RET
: 사각형 모양MORPH_ELLIPSE
: 타원형 모양MORPH_CROSS
: 십자 모양
ksize
: structuring element 사이즈
- Parameters
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
rect_kernel
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
ellipse_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
ellipse_kernel
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)
cross_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5))
cross_kernel
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, rect_kernel)
cv2_imshow(gradient)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, ellipse_kernel)
cv2_imshow(gradient)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, cross_kernel)
cv2_imshow(gradient)
이미지 기울기(Image Gradients)
- Gradient(기울기)는 영상의 edge 및 그 방향을 찾을 때 활용됨
- 이미지(x,y)에서의 벡터값(밝기와 밝기의 변화하는 방향)을 구해서 해당 pixel이 edge에 얼마나 가까운지, 그 방향이 어디인지 알 수 있음
Sobel & Scharr Filter
cv2.Sobel()
- Gaussian smoothing과 미분을 이용
- 노이즈가 있는 이미지에 적용하면 좋음
- X축과 Y축을 미분하는 방법으로 경계값을 계산
파라미터 | 설명 |
---|---|
src |
input image |
ddepth |
output image의 depth, -1 이면 input image와 동일 |
dx |
x축 미분 차수 |
dy |
y축 미분 차수 |
ksize |
kernel size(ksize x ksize) |
cv2.Scharr()
:cv2.Sobel()
과 동일 하지만ksize()
가 soble의 3x3보다 정확하게 적용됨
!wget -O travel.jpg https://c1.wallpaperflare.com/preview/49/805/166/airport-flights-scoreboard-flight.jpg
--2021-10-25 09:49:20-- https://c1.wallpaperflare.com/preview/49/805/166/airport-flights-scoreboard-flight.jpg
Resolving c1.wallpaperflare.com (c1.wallpaperflare.com)... 104.21.2.147, 172.67.129.81, 2606:4700:3031::ac43:8151, ...
Connecting to c1.wallpaperflare.com (c1.wallpaperflare.com)|104.21.2.147|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 111173 (109K) [image/jpeg]
Saving to: ‘travel.jpg’
travel.jpg 100%[===================>] 108.57K --.-KB/s in 0.02s
2021-10-25 09:49:20 (4.89 MB/s) - ‘travel.jpg’ saved [111173/111173]
img = cv2.imread('/content/travel.jpg', 0)
img.shape
(607, 910)
plt.imshow(img, 'gray')
plt.show()
sobelx = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=3)
plt.imshow(sobelx, 'gray')
plt.show()
sobely = cv2.Sobel(img, cv2.CV_8U, 0, 1, ksize=3)
plt.imshow(sobely, 'gray')
plt.show()
sobelx2 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
plt.imshow(sobelx2, 'gray')
plt.show()
sobely2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
plt.imshow(sobely2, 'gray')
plt.show()
Laplacian 함수
cv2.Laplacian()
- 이미지의 가로와 세로에 대한 Gradient를 2차 미분한 값
- Sobel filter에 미분의 정도가 더해진 것과 비슷함
- (dx와 dy가 2인 경우) blob(주위의 pixel과 확인한 pixel 차이를 나타내는 덩어리) 검출에 많이 사용됨
파라미터 | 설명 |
---|---|
src |
source image |
ddepth |
output image의 depth |
plt.imshow(img, 'gray')
plt.show()
laplacian = cv2.Laplacian(img, cv2.CV_8U)
plt.imshow(laplacian, 'gray')
plt.show()
laplacian2 = cv2.Laplacian(img, cv2.CV_64F)
plt.imshow(laplacian2, 'gray')
plt.show()
Canny Edge Detection
cv2.Canny()
- 가장 유명한 Edge Detection 방법
Noise Reduction
- 이미지의 Noise를 제거
- 이때 5x5의 Gaussian filter를 이용
Edge Gradient Detection
- 이미지에서 Gradient의 방향과 강도를 확인
- 경계값에서는 주변과 색이 다르기 때문에 미분값이 급속도로 변하게 됨
- 이를 통해 경계값 후보군을 선별
Non-maximun Suppression
- 이미지의 pixel을 Full scan하여 edge가 아닌 pixel은 제거
Hysteresis Thresholding
- edge로 판단된 pixel이 진짜 edge인지 판별하는 작업 진행
- maxVal과 minVal(임계값)을 설정하여 maxVal 이상은 강한 edge, min과 max 사이는 약한 edge로 설정
- 약한 edge가 진짜 edge인지 확인하기 위해 강한 edge와 연결이 되어 있으면 edge로 판단하고 그렇지 않으면 제거
파라미터 설명 image
8-bit input image threshold1
Hysteresis Thredsholding 작업에서의 min 값 threshold2
Hysteresis Thredsholding 작업에서의 max 값
plt.imshow(img, 'gray')
plt.show()
canny = cv2.Canny(img, 30, 70)
plt.imshow(canny, 'gray')
plt.show()
댓글남기기