38 분 소요


  • 입력 받은 영상을 사용 목적에 맞게 적절하게 처리하여 보다 개선된 영상을 생성하는 것
  • 입력 영상에 있는 잡음(noise) 제거, 영상의 대비(contrast) 개선, 관심영역(region of interest) 강조, 영역 분할(segmentation), 입축 및 저장 등
  • 저수준 영상 처리(좁은 의미의 영상 처리)
    • 영상 획득
    • 영상 향상
    • 영상 복원
    • 변환 처리
    • 영상 압축
  • 고수준 영상 처리(컴퓨터 비전)
    • 영상 분할
    • 영상 표현
    • 영상 인식

영상처리의 역사

  • 영상처리의 시작
    • 1920년대 초반 런던과 뉴욕 간에 해저 케이블을 통한 신문사들이 사진 전송
  • 본격적인 영상 처리 위한 기술
    • 1940년대 폰 노이만의 디지털 컴퓨터의 개념 시작
    • 1950년 이후 트랜지스터, IC, 마이크로프로세서 같은 하드웨어 발달
    • 1950 ~ 60년대 프로그램 언어의 발달과 운영체제 등의 소프트웨어 기술 발달
  • 본격적인 영상 처리 시작
    • 우주 탐사 계획인 아폴로 계획과도 관련, 우주선에서 보낸 훼손된 영상의 복원 연구
  • 1970년대 영상 처리 분야 더욱 발전
    • CT, MRI 등의 의료 분야
    • 원격 자원 탐사, 우주 항공 관련 분야
  • 1990년대 컴퓨터 비전과 응용 분야 급속히 확장
    • 인터넷 시대에 영상검색, 영상전송, 영상광고
    • 디지털 방송 관련 컴퓨터 그래픽스, 디지털 카메라 보급

영상처리 응용 분야

  • 의료 분야(방사선, 초음파
    • 컴퓨터 단층 촬영(CT), 자기 공명 영상(MRI)
    • 양전자 단층 촬영(PET)

  • 방송 통신 분야
    • 디지털 방송 서비스로 인한 영상처리 기술 발달
    • 스포츠 방송 분야에 영상 처리 기술 적용, 가상 광고 분야

  • 공장 자동화 분야
    • 산업용 카메라로 제품 품질 모니터링 및 불량 제거

  • 기상 및 지질 탐사 분야
    • 방대한 기상 정보를 이용의 시각화
    • 다양한 주파수의 사진들을 영상 처리 기술로 표현

  • 애니메이션 및 게임 분야
    • 촬영된 영상과 그래픽 기술이 조합
    • 현실감 향상

  • 출판 및 사진 분야
    • 영상 생성, 품질 향상, 색상을 조작 등의 작업을 위해 영상 처리 기술 사용
    • 기존 영상에 영상 처리 기술을 융합하여 새로운 합성 영상

컴퓨터 비전 처리 단계

영상처리_프로세스.png

  • 전처리 단계
    • 주로 영상처리 기술 사용
    • 다양한 특징 추출 : 엣지(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로 갈수록 밝아짐

image.png

이미지 파일 형식

  • BMP
    • 픽셀 데이터를 압축하지 않은 상태로 저장
    • 파일 구조 간단하지만 용량이 매우 큼
  • JPG(JPEG)
    • 손실 압축(lossy compression) 사용
    • 원본 영상으로부터 픽셀값이 미세하게 달라짐
    • 파일 용량 크기가 크게 감소하는 이점
    • 디지털 카메라
  • GIF
    • 무손실 압축(losses compression)
    • 움직이는 그림인 Animation GIF 지원
    • 256 이하의 색상을 가진 영상만을 저장하고, 화질이 매우 떨어짐
  • PNG
    • Portable Network Graphics
    • 무손실 압축 사용
    • 용량은 큰 편이지만 픽셀값이 변경되지 않음
    • α 채널을 지원하여 일부분을 투명하게 설정 가능

OpenCV

  • 실시간 컴퓨터 비전을 목적으로 인텔(Intel)에서 개발
  • 실시간 이미지 프로세싱에 중점을 둔 한 프로그래밍 라이브러리
  • TensorFlow, PyTorch 및 Caffe의 딥러닝 프레임워크 지원

이미지 읽기/쓰기

  • 이미지는 배열로 표현 가능(Numpy)

이미지 읽기(PIL)

  • pillow, matplotlibOpenCV 모두 가능
  • 구글 코랩(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

png

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

png

pic_copy = pic_arr.copy()
plt.imshow(pic_copy)
plt.show()

png

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

png

plt.imshow(pic_copy[:,:,0], cmap='gray')
plt.show()

png

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

png

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

png

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

png

pic_green = pic_arr.copy()
pic_green[:,:,0] = 0
pic_green[:,:,2] = 0
plt.imshow(pic_green)
plt.show()

png

pic_blue = pic_arr.copy()
pic_blue[:,:,0] = 0
pic_blue[:,:,1] = 0
plt.imshow(pic_blue)
plt.show()

png

이미지 출력(OpenCV)

  • from google.colab.patches import cv2_imshow
    • 원래는 cv2.imshow
from google.colab.patches import cv2_imshow
cv2_imshow(pic_arr)

png

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)

png

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)

png

plt.imshow(img)
plt.show()

png

img_temp = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_temp)
plt.show()

png

img_gray = cv2.imread('/content/lion.jpg', cv2.IMREAD_GRAYSCALE)
print(img_gray.shape)
(480, 644)
cv2_imshow(img_gray)

png

plt.imshow(img_gray)
plt.show()

png

plt.imshow(img_gray, cmap='gray')
plt.show()

png

plt.imshow(img_gray, cmap='magma')
plt.show()

png

이미지 쓰기

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)

png

도형 그리기

  • 다양한 도형을 그릴 수 있음
  • 도형을 그리는 좌표가 해당 범위를 넘어가면 이미지에 표현되지 않음
  • 얼굴 검출 알고리즘: 영상 위에 검출한 얼굴 영역을 사각형이나 원으로 표시
  • 차선 검출 알고리즘: 차선을 정확하게 검출했는지 확인하기 위해 도로 영상 위에 선으로 표시
img = np.zeros((512,512,3), np.uint8)
plt.imshow(img)
plt.show()

png

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

png

사각형(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()

png

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

png

img = cv2.circle(img, (50,450), 50, (0, 255, 255), 2)
plt.imshow(img)
plt.show()

png

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

png

img = cv2.ellipse(img, (256,256), (150,50), 45, 0, 360, (255,255,255), 2)
plt.imshow(img)
plt.show()

png

img = cv2.ellipse(img, (256,256), (150,10), 135, 0, 270, (0,0,255), 2)
plt.imshow(img)
plt.show()

png

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

png

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

png

텍스트(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()

png

컬러 매핑(Color Mapping)

  • 주로 그레이 스케일(Grayscale), 트루 컬러(True Color, RGB) 이미지를 많이 활용
  • 다양한 색 공간(ex, HSV, YCrCB 등)이 존재하고 이들을 변환할 수 있음
  • 컬러 영상 처리에서 HSVHSL은 같은 색 공간을 이용하여 색상 구분에 용이하고, 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 B
    • OpenCV : B G R
plt.imshow(origin_img)
plt.show()

png

img_rgb = cv2.cvtColor(origin_img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()

png

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

png

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

png

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

png

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

png

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

png

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

png

히스토그램 평탄화

  • 이미지의 히스토그램이 특정 영역에 너무 집중되어 있으면 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()

png

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

png

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

png

  • 위 결과에서 밝은 부분은 균일화가 적용되어 어두워졌지만, 일부 이미지 영역은 너무 어두워짐
  • 이문제를 해결하기 위해서 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()

png

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)

png

plt.imshow(hist)
plt.show()

png

  • 위 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)

png

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)

png

(320, 480, 3)
cv2_imshow(zoom1)
print(zoom1.shape)

png

(1280, 1920, 3)
cv2_imshow(zoom2)
print(zoom2.shape)

png

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

png

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)

png

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

png

rs1 = cv2.flip(img, 1)

plt.imshow(rs1)
plt.show()

png

rs1 = cv2.flip(img, 0)

plt.imshow(rs1)
plt.show()

png

rs1 = cv2.flip(img, -1)

plt.imshow(rs1)
plt.show()

png

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)

png

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

png

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)

png

  • 좌표점은 (왼쪽 위) -> (오른쪽 위) -> (오른쪽 아래) -> (왼쪽 아래)
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)

png

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

png

이미지 연산(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)

png

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)

png

인덱싱

print(tree[:100,:100].shape)
(100, 100, 3)
cv2_imshow(tree[400:500,400:500])

png

white_box = tree[400:500,400:500]
white_box = [255,255,255]
tree[400:500,400:500] = white_box
cv2_imshow(tree)

png

이미지 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)

png

t = tree[:500,760:]
cv2_imshow(t)

png

tree[:500,100:300] = t
cv2_imshow(tree)

png

tree[100:600,400:600] = t
cv2_imshow(tree)

png

이미지의 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)

png

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

png

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

png

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

png

이미지 더하기

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

png

  • Numpy 연산
rs2 = dog1 + dog2
cv2_imshow(rs2)

png

비트 연산

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

png

bk_img = np.zeros((800,880,3))
plt.imshow(bk_img)
plt.show()

png

bk_img[:,:440,:] = (255,255,255)
  • 이미지의 dtype을 int형으로 변환
img = bk_img.astype(np.uint8)
plt.imshow(img)
plt.show()

png

bitwise_and 연산

rs = cv2.bitwise_and(img, star)
plt.imshow(rs)
plt.show()

png

bitwise_or 연산

rs = cv2.bitwise_or(img, star)
plt.imshow(rs)
plt.show()

png

bitwise_not 연산

rs = cv2.bitwise_not(img)
plt.imshow(rs)
plt.show()

png

rs = cv2.bitwise_not(star)
plt.imshow(rs)
plt.show()

png

bitwise_xor 연산

rs = cv2.bitwise_xor(img, star)
plt.imshow(rs)
plt.show()

png

tmp_star = cv2.bitwise_not(star)
rs = cv2.bitwise_xor(img, tmp_star)
plt.imshow(rs)
plt.show()

png

비트 연산 예시

  • 이미지에 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()

png

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

png

plt.imshow(mask_inv, cmap='gray')
plt.show()

png

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)

png

bk_img[:rows,:cols] = dst
cv2_imshow(bk_img)

png

이미지 블렌딩(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()

png

plt.imshow(img2)
plt.show()

png

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

png

plt.imshow(img2)
plt.show()

png

blended = cv2.addWeighted(src1=img1, alpha=0.5, src2=img2, beta=0.5, gamma=0)
plt.imshow(blended)
plt.show()

png

blended = cv2.addWeighted(src1=img1, alpha=0.8, src2=img2, beta=0.1, gamma=0)
plt.imshow(blended)
plt.show()

png

이미지 이진화(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()

png

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

png

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

png

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

png

적응 임계처리

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

png

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

png

이미지 필터링(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()

png

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

png

이미지 샤프닝(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()

png

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

png

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

png

이미지 블러링(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_64F
      • ksize : 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개)
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()

png

dst1 = cv2.blur(img, (7,7))
plt.title('Averaging Blurring')
plt.imshow(dst1)
plt.show()

png

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

png

dst2 = cv2.GaussianBlur(img, (5,5), 0)
plt.title('GaussianBlur')
plt.imshow(dst2)
plt.show()

png

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

png

dst3 = cv2.medianBlur(img, 9)
plt.title('Median Blur')
plt.imshow(dst3)
plt.show()

png

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

png

dst4 = cv2.bilateralFilter(img, 9, 75, 75)
plt.title('Bilateral Filter')
plt.imshow(dst4)
plt.show()

png

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

png

형태학적 변환(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)

png

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)

png

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)

png

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)

png

opening = cv2.morphologyEx(opening, cv2.MORPH_OPEN, kernel)
cv2_imshow(opening)

png

closing = cv2.imread('/content/closing.png')
cv2_imshow(closing)

png

closing = cv2.morphologyEx(closing, cv2.MORPH_CLOSE, kernel)
cv2_imshow(closing)

png

Morphological Gradient

  • dilation과 erosion의 차이
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv2_imshow(gradient)

png

Top Hat

  • 입력 이미지와 opening 이미지와의 차이
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2_imshow(tophat)

png

Black Hat

  • 입력 이미지와 closing 이미지와의 차이
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2_imshow(blackhat)

png

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 사이즈
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)

png

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, ellipse_kernel)
cv2_imshow(gradient)

png

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, cross_kernel)
cv2_imshow(gradient)

png

이미지 기울기(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()

png

sobelx = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=3)
plt.imshow(sobelx, 'gray')
plt.show()

png

sobely = cv2.Sobel(img, cv2.CV_8U, 0, 1, ksize=3)
plt.imshow(sobely, 'gray')
plt.show()

png

sobelx2 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
plt.imshow(sobelx2, 'gray')
plt.show()

png

sobely2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
plt.imshow(sobely2, 'gray')
plt.show()

png

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

png

laplacian = cv2.Laplacian(img, cv2.CV_8U)
plt.imshow(laplacian, 'gray')
plt.show()

png

laplacian2 = cv2.Laplacian(img, cv2.CV_64F)
plt.imshow(laplacian2, 'gray')
plt.show()

png

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

png

canny = cv2.Canny(img, 30, 70)
plt.imshow(canny, 'gray')
plt.show()

png

댓글남기기