--- 본 포스팅은 데이콘 서포터즈 "데이크루 2기" 활동의 일환입니다 ---
- 안녕하세요 데이콘 서포터즈 데이크루 2기 포스(POS)팀의 Rahites입니다 :)
- POS팀은 Python OpenCV Study의 약자로 활동 기간동안 저희 팀은 '파이썬으로 만드는 OpenCV 프로젝트' 책을 가지고 OpenCV를 공부해보고 프로젝트를 진행할 것입니다.
- 자세한 스터디 계획과 운영 방안은 아래의 포스팅에서 확인하실 수 있습니다.
https://dacon.io/codeshare/4759?utm_source=dacrew&utm_medium=432727&utm_campaign=dacrew_2
- 본격적으로 OpenCV를 공부하기 전에 '파이썬으로 만드는 OpenCV 프로젝트' 책 3장에 저술되어 있는 'Numpy와 Matplotlib'에 대해 소개하겠습니다. 데이콘 코드공유 사이트에 포스팅 되는 페이지는 아래링크를 통해 접속하실 수 있습니다.
https://dacon.io/codeshare/4768?utm_source=dacrew&utm_medium=432727&utm_campaign=dacrew_2
- 그럼 시작하겠습니다.
3. Numpy와 Matplotlib¶
3.1. Numpy¶
3.1.1. 이미지와 Numpy¶
파이썬에서 실시간으로 컴퓨터 비전 프로그래밍을 실행할 때는 OpenCV 라이브러리를 주로 사용합니다. cv2 패키지를 import해서 이를 사용할 수 있는데, 이 때 이미지나 동영상을 읽어들이는 함수 cv2.imread()는 Numpy배열을 반환합니다. 따라서 OpenCV를 파이썬 언어로 프로그래밍한다는 것은 Numpy배열을 다룬다는 것과 같은 말입니다. Numpy 배열에서 정보를 얻는 기본 속성은 다음과 같습니다.
- ndim : 차원(축)의 수
- shape : 각 차원의 크기(튜플)
- size : 전체 요소의 개수, shape의 각 항목의 곱
- dtype : 요소의 데이터 타입
- itemsize : 각 요소의 바이트 크기
이미지는 여러 개의 픽셀들의 값으로 구성되기 때문에 수많은 행과 열로 구성된 픽셀 데이터의 모음으로 볼 수 있습니다.
import cv2
img = cv2.imread('../img_1/blank_500.jpg')
type(img)
numpy.ndarray
ndarray는 N-Dimensional Array의 약자로 다차원 배열을 의미합니다. OpenCV는 기본적으로 이미지를 3차월 배열인 '행x열x채널'로 표현합니다.
print(img.ndim)
print(img.shape)
print(img.size) # 전체 요소의 개수
print(img.dtype) # 부호없는 8비트 ( 이미지 픽셀은 음수나 소수점이 없다)
print(img.itemsize) # 각 요소의 크기가 1바이트
3 (500, 500, 3) 750000 uint8 1
Numpy 배열은 모든 요소에 동일한 연산을 수행하는 브로드캐스팅 연산, 선형대수학의 벡터와 행렬 연산, 푸리에 변환 등 이미지 프로세싱이나 컴퓨터 비전 분야에 활용할 수 있는 방대한 기능을 제공합니다. 물론 영상 처리와 컴퓨터 비전 알고리즘이 매우 복잡하고 구현이 어렵기 때문에 최적화된 OpenCV 라이브러리를 쓰는 것이 효과적이지만, OpenCV에 따로 구현되지 않은 간단한 연산은 Numpy로 직접 처리해야 하기 때문에 기초적인 Numpy 사용법을 알고 있을 필요가 있습니다.
3.1.2. Numpy 배열 생성¶
Numpy 배열은 값을 가지고 생성하는 방법, 크기만 지정해서 생성하는 방법으로 나뉩니다. 크기만 지정해서 생성하는 방법은 다시 특정한 초기 값을 모든 요소에 지정하는 경우와 값의 범위를 지정해서 순차적으로 증가 또는 감소하는 값을 갖게하는 방법으로 나눌 수 있습니다.
- 값으로 생성 : array()
- 초기 값으로 생성 : empty(), zeros(), ones(), full()
- 기존 배열로 생성 : empty_like(), zeros_like(), ones_like(), full_like()
- 순차적인 값으로 생성 : arange()
- 난수로 생성 : random.rand(), random.randn()
3.1.3. 값으로 생성¶
배열 생성에 사용할 값을 가지고 있는 경우에는 numpy.array() 함수로 쉽게 생성할 수 있습니다.
- numpy.array(list [, dtype]) : 지정한 값들로 Numpy 배열을 생성
- list : 배열 생성에 사용할 값을 갖는 파이썬 리스트 객체
- dtype : 데이터 타입 ( 생략하면 값에 의해 자동 결정 )
- int8, int16, int32, int64 : 부호 있는 정수
- uint8, uint16, uint32, uint64 : 부호 없는 정수
- float16, float32, float64, float128 : 부동 소수점을 갖는 실수
- complex64, complex128, complex256 : 부동 소수점을 갖는 복소수
- bool : 불리언
이 외에도 훨씬 더 많은 dtype이 존재합니다.
import numpy as np
a = np.array([1,2,3,4])
print('a :',a)
print('a.dtype : ',a.dtype)
print('a.shape : ',a.shape)
a : [1 2 3 4] a.dtype : int32 a.shape : (4,)
b = np.array([[1,2,3,4],[5,6,7,8]])
print(b)
print(b.dtype)
print(b.shape)
[[1 2 3 4] [5 6 7 8]] int32 (2, 4)
c = np.array([1,2,3.14,4])
print(c)
print(c.dtype)
[1. 2. 3.14 4. ] float64
# 명시적으로 dtype 지정
d = np.array([1,2,3,4], dtype=np.float32)
print('d : ',d)
print('d.dtype : ',d.dtype)
d : [1. 2. 3. 4.] d.dtype : float32
# Numpy 생성함수는 dtype과 동일한 이름의 함수를 제공한다
e = np.uint8([1,2,3,4])
print(e)
print(e.dtype)
[1 2 3 4] uint8
3.1.4. 크기와 초기 값으로 생성¶
Numpy 배열을 생성할 때 사용할 값을 가지고 있지 않은 경우가 많기 때문에 초기 값을 지정해서 생성하는 방법을 사용합니다.
- numpy.empty(shape [, dtype]) : 초기화되지 않는 값(쓰레기 값)으로 배열 생성
- shape : 튜플, 배열의 각 차수의 크기 지정
- numpy.zeros(shape [,dtype]) : 0으로 초기화된 배열 생성
- numpy.ones(shape [,dtype]) : 1로 초기화된 배열 생성
- numpy.full(shape, fill_value [,dtype]) : fill_value로 초기화된 배열 생성
# 쓰레기 값, 메모리 할당만 받고 초기화 없이 반환
a = np.empty((2,3))
print(a)
print(a.dtype)
[[0. 0. 0.] [0. 0. 0.]] float64
# 쓰레기 값을 갖는 배열을 어떤 값으로 초기화
# ndarray.fill(value) : 배열의 모든 요소를 value로 채움
a.fill(255)
print(a)
[[255. 255. 255.] [255. 255. 255.]]
b = np.zeros((2,3))
print(b)
print(b.dtype)
[[0. 0. 0.] [0. 0. 0.]] float64
c = np.zeros((2,3), dtype = np.int8)
print(c.dtype)
int8
d = np.ones((2,3), dtype = np.int16)
print(d)
[[1 1 1] [1 1 1]]
e = np.full((2,3,4), 255, dtype = np.uint8)
print(e)
[[[255 255 255 255] [255 255 255 255] [255 255 255 255]] [[255 255 255 255] [255 255 255 255] [255 255 255 255]]]
새로운 배열을 생성할 때 기존에 있던 배열과 같은 크기의 배열을 만들때에는 위의 함수들에 _like를 붙인 함수를 사용합니다. ( 주로 이미지를 읽어서 필요한 연산을 한 후에 결과 이미지를 생성할 때 원본 이미지와 동일한 크기의 배열을 생성해야하는 경우가 많은데 이 때 이런 함수들을 주로 사용합니다 )
empty_like, zeros_like, ones_like, full_like
img = cv2.imread('../img/girl.jpg')
print(img.shape)
(426, 640, 3)
a = np.empty_like(img)
b = np.zeros_like(img)
c = np.ones_like(img)
d = np.full_like(img, 255)
print(a.shape, b.shape, c.shape, d.shape)
(426, 640, 3) (426, 640, 3) (426, 640, 3) (426, 640, 3)
3.1.5. 시퀀스와 난수로 생성¶
Numpy 배열을 생성하는 방법 중에는 일정한 범위 내에서 순차적인 값을 갖게하는 방법과 난수로 채우는 방법이 있습니다.
- numpy.arange([start=0, ] stop [, step=1, dtype = float64]) : 순차적인 값으로 생성
- start : 시작 값
- stop : 종료 값, 범위에 포함되는 수는 ~ stop -1
- step : 증가 값
- dtype : 데이터 타입
- numpy.random.rand(array크기) : 0과 1 사이의 무작위 수 생성
- array크기를 생략하면 난수 1개 반환, 나머지는 해당 크기만큼 값을 반환
- numpy.random.randn(array크기) : 표준 정규 분포를 따르는 무작위 수 생성
a = np.arange(5)
print(a)
print(a.dtype)
print(a.shape)
[0 1 2 3 4] int32 (5,)
b = np.arange(5.0)
print(b)
print(b.dtype)
print(b.shape)
[0. 1. 2. 3. 4.] float64 (5,)
c = np.arange(3,9,2) # 지정한 범위의 마지막 값은 항목에 포함되지 않습니다.
print(c)
[3 5 7]
print(np.random.rand())
print(np.random.randn())
print(np.random.rand(2,3))
print(np.random.randn(2,3))
'''
이 함수들은 결과 값이 소수점을 갖고 특정 범위 내에서 난수를 추출하기 때문에
이미지 작업에 필요한 원하는 범위 내에서 난수를 발생하기 위해서는
브로드캐스팅 연산과 dtype 변경이 필요할 때가 많습니다.
'''
0.7763742611426422 0.44091348588636564 [[0.27538784 0.48897448 0.77882828] [0.21887406 0.96737756 0.2027582 ]] [[ 1.5677613 -0.29543746 0.2248752 ] [-0.02302106 0.23251049 -0.17579124]]
'\n이 함수들은 결과 값이 소수점을 갖고 특정 범위 내에서 난수를 추출하기 때문에\n이미지 작업에 필요한 원하는 범위 내에서 난수를 발생하기 위해서는\n브로드캐스팅 연산과 dtype 변경이 필요할 때가 많습니다.\n'
3.1.6. dtype 변경¶
- ndarray.astype(dtype)
- dtype : 변경하고 싶은 dtype, 문자열 또는 dtype
- numpy.uintX(array) : array를 부호 없는 정수 타입으로 변경해서 반환
- X : 8, 16, 32, 64
- numpy.intX(array) : array를 int 타입으로 변경해서 반환
- X : 8, 16, 32, 64
- numpy.floatX(array) : array를 float 타입으로 변경해서 반환
- X : 16, 32, 64, 128
- numpy.complexX(array) : array를 복소수(complex) 타입으로 변경해서 반환
- X : 64, 128, 256
3.1.7. 차원 변경¶
- ndarray.reshape(newshape) : ndarray의 shape를 newshape로 차원 변경
- numpy.reshape(ndarray, newshape) : ndarray의 shape를 newshape로 차원 변경
- ndarray : 원본 배열 객체
- newshape : 변경하고자 하는 새로운 shape
- numpy.ravel(ndarray) : 1차원 배열로 차원 변경
- ndarray :변경할 원본 배열
- ndarray.T : 전치배열 (transpose)
# arange함수는 1차원만 생성 가능
a = np.arange(6)
print(a)
b = a.reshape(2,3)
print(b)
c = np.reshape(a,(2,3))
print(c)
[0 1 2 3 4 5] [[0 1 2] [3 4 5]] [[0 1 2] [3 4 5]]
# shape를 지정할 때 -1을 포함해서 전달 -> 해당 차수에 대해서는 크기를 지정하지 않겠다
d = np.arange(100).reshape(2,-1)
print(d)
print(d.shape)
[[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49] [50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]] (2, 50)
e = np.arange(100).reshape(-1, 5)
print(e)
print(e.shape)
[[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19] [20 21 22 23 24] [25 26 27 28 29] [30 31 32 33 34] [35 36 37 38 39] [40 41 42 43 44] [45 46 47 48 49] [50 51 52 53 54] [55 56 57 58 59] [60 61 62 63 64] [65 66 67 68 69] [70 71 72 73 74] [75 76 77 78 79] [80 81 82 83 84] [85 86 87 88 89] [90 91 92 93 94] [95 96 97 98 99]] (20, 5)
# 1차원으로 재정렬 : reshape, ravel
f = np.zeros((2,3))
print(f)
print(f.reshape((6,))) # 직접 요소의 개수를 입력해야해 불편
print(f.reshape(-1))
print(np.ravel(f))
[[0. 0. 0.] [0. 0. 0.]] [0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0.]
# 전치배열
g = np.arange(10).reshape(2,-1)
print(g)
print(g.T)
[[0 1 2 3 4] [5 6 7 8 9]] [[0 5] [1 6] [2 7] [3 8] [4 9]]
3.1.8. 브로드캐스팅 연산¶
Numpy 배열에 산술 연산을 수행했을 때 전체 원소에 같은 연산이 적용되는 것을 의미합니다. 예를 들어 리스트의 모든 항목 값을 1씩 증가시키려면 반복문을 활용해야 하는데 Numpy 배열은 한번의 연산으로 같은 값을 출력할 수 있습니다.
mylist = list(range(10))
print(mylist)
for i in range(len(mylist)):
mylist[i] += 1
print(mylist)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# numpy 배열 사용, + 뿐만 아니라 모든 산술 연산이 가능합니다.
import numpy as np
a = np.arange(10)
print(a)
print(a+1)
[0 1 2 3 4 5 6 7 8 9] [ 1 2 3 4 5 6 7 8 9 10]
# Booolean을 반환하는 비교 연산도 가능합니다.
print(a>0)
[False True True True True True True True True True]
# 배열 끼리의 연산도 가능합니다.
a = np.arange(10, 60, 10)
b = np.arange(1, 6)
print(a+b)
print(a*b)
[11 22 33 44 55] [ 10 40 90 160 250]
# 두 배열의 shape가 완전히 동일하거나 둘 중 하나가 1차원이면서 1차원 배열의 축의 길이가 같아야 합니다.
a = np.ones((2,3))
print(a)
c = np.arange(3)
print(c)
print(a+c)
[[1. 1. 1.] [1. 1. 1.]] [0 1 2] [[1. 2. 3.] [1. 2. 3.]]
3.1.9. 인덱싱과 슬라이싱¶
Numpy 배열은 파이썬의 리스트처럼 인덱스로 각 요소에 접근 할 수 있다. 당연히 배열의 차원에 따라서 인덱스의 개수도 달라집니다.
# 1차원
a = np.arange(10)
print(a)
print(a[5])
# 2차원
b = np.arange(12).reshape(3,4)
print(b)
print(b[1])
print(b[1,2])
[0 1 2 3 4 5 6 7 8 9] 5 [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [4 5 6 7] 6
# 값 변경은 인덱스를 활용해 정확히 1개의 요소를 지정하면 1개의 요소만 변경되지만,
# 인덱스를 적게 지정해서 행 단위로 지정하면 브로드캐스팅 연산이 일어나서 해당 단위를 모두 같은 값으로 변경합니다.
print(a)
a[5]=9
print(a)
print(b)
b[0]=0
print(b)
b[1,2]=99
print(b)
[0 1 2 3 4 5 6 7 8 9] [0 1 2 3 4 9 6 7 8 9] [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [[ 0 0 0 0] [ 4 5 6 7] [ 8 9 10 11]] [[ 0 0 0 0] [ 4 5 99 7] [ 8 9 10 11]]
a = np.arange(10)
print(a[2:5])
print(a[5:])
b = np.arange(12).reshape(3,4)
print(b)
print(b[0:2,1]) # 0 ~ 1 행의 1열 요소
print(b[0:2, 1:3])
b[0:2,1:3] = 0
print(b)
[2 3 4] [5 6 7 8 9] [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [1 5] [[1 2] [5 6]] [[ 0 0 0 3] [ 4 0 0 7] [ 8 9 10 11]]
슬라이싱으로 전체 배열 중 일부를 다른 변수에 할당하는 경우 별도의 배열이 아닌 슬라이싱 해당 영역에 대한 참조일 뿐이기 때문에 값의 변경은 원본에도 그대로 반영됩니다. 만약 복제본을 사용하고 싶다면 ndarray.copy() 함수를 사용해야 합니다.
print(b)
bb = b[0:2, 1:3] # b에서 슬라이싱해온 값을 bb변수에 저장
print(bb)
bb[0] = 99 # bb 변수의 0 값을 99로 변경
print(b) # b는 건들지 않았지만 슬라이싱한 결과인 bb의 값을 변경했기 때문에 b의 값또한 변경된 모습
[[ 0 0 0 3] [ 4 0 0 7] [ 8 9 10 11]] [[0 0] [0 0]] [[ 0 99 99 3] [ 4 0 0 7] [ 8 9 10 11]]
3.1.10. 팬시 인덱싱¶
배열 인덱스에 다른 배열을 전달해서 원하는 요소를 선택하는 방법을 팬시 인덱싱이라고 합니다. 전달하는 배열에 숫자를 포함하고 있으면 해당 인덱스에 맞게 선택되고, 배열에 boolean 값을 포함하면 True 인 값을 갖는 요소만 선택됩니다.
a = np.arange(5)
print(a)
print(a[[1,3]])
print(a[[True, False, True, False, True]])
[0 1 2 3 4] [1 3] [0 2 4]
Numpy 배열에 비교 연산을 하면 개별 요소들이 조건을 만족하는지를 알 수 있습니다. 반대로 boolean 값을 갖는 배열을 배열의 인덱스 대신 사용하면 True 값 위치의 값들만 얻을 수 있는데, 이 둘을 한번에 합해서 실행하면 원하는 조건의 값만을 얻을 수 있습니다.
import numpy as np
a = np.arange(10)
print('a : ',a)
b = a > 5
print('b : ',b)
print('a[b] : ',a[b])
print('a[a>5] : ',a[a>5])
a[a>5] = 1 # a 중에 5 초과인 원소들을 1로 ( 브로드캐스팅 연산 )
print('변경된 a :',a)
a : [0 1 2 3 4 5 6 7 8 9] b : [False False False False False False True True True True] a[b] : [6 7 8 9] a[a>5] : [6 7 8 9] 변경된 a : [0 1 2 3 4 5 1 1 1 1]
3.1.11. 병합과 분리¶
2개 이상의 Numpy 배열을 병합하는 방법은 크게 단순히 배열을 이어 붙여서 크기를 키우는 방법과 새로운 차원을 만들어 서로서로 끼워넣는 방법, 2가지가 있습니다. 이를 이해하기 위해서는 축(axis)의 개념을 알아야 하는데 shape가 (10, 20, 3)이면 3개의 축이고 10은 0번축, 20은 1번축으로 생각하고, 축을 기준으로 작업을 한다는 뜻은 shape의 각 순서에 따라 작업을 한다는 뜻입니다.
- numpy.hstack(arrays) : arrays 배열을 수평으로 병합
- numpy.vstack(arrays) : arrays 배열을 수직으로 병합
- numpy.concatenate(arrays, axis=0) : arrays 배열을 지정한 축 기준으로 병합
- numpy.stack(arrays, axis=0) : arrays 배열을 새로운 축으로 병합
- arrays : 병합 대상 배열(튜플)
- axis : 작업할 대상 축 번호
# vstack, hstack
a = np.arange(4).reshape(2,2)
print(a)
b = np.arange(10, 14).reshape(2,2)
print(b)
print(np.vstack((a,b)))
print(np.hstack((a,b)))
print(np.concatenate((a,b),0)) # 수직으로 병합
print(np.concatenate((a,b),1)) # 수평으로 병합
[[0 1] [2 3]] [[10 11] [12 13]] [[ 0 1] [ 2 3] [10 11] [12 13]] [[ 0 1 10 11] [ 2 3 12 13]] [[ 0 1] [ 2 3] [10 11] [12 13]] [[ 0 1 10 11] [ 2 3 12 13]]
# stack
a = np.arange(12).reshape(4,3)
b = np.arange(10, 130, 10).reshape(4,3)
print(a)
print(b)
c = np.stack((a,b),0) # 4행 3열인 2차원 배열은 결합시 3차원이 되는데 이때 축번호가 0으로 전달 -> 0위치에 2가 전달
print(c.shape)
d = np.stack((a,b),1)
print(d.shape)
e = np.stack((a,b),2) # -1일 경우에는 마지막 축 번호의 의미를 가집니다
print(e.shape)
[[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]] [[ 10 20 30] [ 40 50 60] [ 70 80 90] [100 110 120]] (2, 4, 3) (4, 2, 3) (4, 3, 2)
- numpy.vsplit(array, indice) : array 배열을 수평으로 분리
- numpy.hsplit(array, indice) : array 배열을 수직으로 분리
- numpy.split(array, indice, axis = 0 ) : array 배열을 axis 축으로 분리
- array : 분리할 배열
- indice : 분리할 개수 또는 인덱스 ( 어떻게 나눌지를 정하는 인자로 정수 = 배열을 그 수로 나눔, 1차워 배열 = 나눌 인덱스 )
- axis : 기준 축 번호
a = np.arange(12)
print(a)
print(np.hsplit(a,3)) # 3개로 나눔
print(np.hsplit(a, [3,6])) # 3이랑 6인덱스에서 나눔
print(np.hsplit(a, [3,6,9]))
print(np.split(a, 3, 0)) # 축이 1개이기 때문에 axis는 0만 사용할 수 있다.
[ 0 1 2 3 4 5 6 7 8 9 10 11] [array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([ 8, 9, 10, 11])] [array([0, 1, 2]), array([3, 4, 5]), array([ 6, 7, 8, 9, 10, 11])] [array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8]), array([ 9, 10, 11])] [array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([ 8, 9, 10, 11])]
b = np.arange(12).reshape(4,3)
print(b)
print(np.vsplit(b,2)) # 수직으로 나눔
print(np.split(b, 2, 0)) # 0번 축을 기준으로 2개로 나눔
print(np.hsplit(b, [1]))
print(np.split(b, [1], 1))
[[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]] [array([[0, 1, 2], [3, 4, 5]]), array([[ 6, 7, 8], [ 9, 10, 11]])] [array([[0, 1, 2], [3, 4, 5]]), array([[ 6, 7, 8], [ 9, 10, 11]])] [array([[0], [3], [6], [9]]), array([[ 1, 2], [ 4, 5], [ 7, 8], [10, 11]])] [array([[0], [3], [6], [9]]), array([[ 1, 2], [ 4, 5], [ 7, 8], [10, 11]])]
3.1.12. 검색¶
- ret = numpy.where(condition, [,t,f]) : 조건에 맞는 요소를 찾기
- ret : 검색 조건에 맞는 요소의 인덱스 또는 변경된 값으로 채워진 배열(튜플)
- condition : 검색에 사용할 조건식
- t, f : 조건에 따라 지정할 값 또는 배열, 배열의 경우 조건에 사용한 배열과 같은 shape
- t : 조건에 맞는 값에 지정할 값이나 배열
- f : 조건에 틀린 값에 지정할 값이나 배열
- numpy.nonzero(array) : array에서 요소 중에 0이 아닌 요소의 인덱스들을 반환
- numpy.all(array [, axis]) : array의 모든 요소가 True인지 검색
- array : 검색 대상 배열
- axis : 검색할 기준 축, 생략하면 모든 요소 검색, 지정하면 축 개수별로 결과 반환
- numpy.any(array [, axis]) : array의 어느 요소이든 True가 있는지 검색
# 배열에서 조건에 맞는 인덱스를 찾아오는 사례
a = np.arange(10, 20)
print(a)
print(np.where(a>15)) # 15보다 큰 값의 인덱스 반환
print(np.where(a>15, 1, 0)) # 15보다 큰 값을 1로 작은 값을 0으로
[10 11 12 13 14 15 16 17 18 19] (array([6, 7, 8, 9], dtype=int64),) [0 0 0 0 0 0 1 1 1 1]
print(np.where(a>15, 99, a)) # 반환만 하고 원본 값에는 영향을 주지 않습니다.
print(a)
[10 11 12 13 14 15 99 99 99 99] [10 11 12 13 14 15 16 17 18 19]
# 다차원 배열인 경우 원하는 요소를 검색만 한다면 해당하는 요소의 인덱스는 여러개를 반환합니다.
b = np.arange(12).reshape(3,4)
print(b)
coords = np.where(b>6)
print(coords)
print(np.stack((coords[0], coords[1]), -1)) # 열로 합침 (-1 -> 1로 바꿔도 같은 결과)
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] (array([1, 2, 2, 2, 2], dtype=int64), array([3, 0, 1, 2, 3], dtype=int64)) [[1 3] [2 0] [2 1] [2 2] [2 3]]
# 배열 요소 중에 0이 아닌 요소를 찾을 때
z = np.array([0,1,2,0,1,2])
print(np.nonzero(z))
zz = np.array([[0,1,2],[1,2,0],[2,0,1]])
print(zz)
coords = np.nonzero(zz)
print(coords) # 위치정보를 axis별 array로 저장
print(np.stack((coords[0], coords[1]), -1))
(array([1, 2, 4, 5], dtype=int64),) [[0 1 2] [1 2 0] [2 0 1]] (array([0, 0, 1, 1, 2, 2], dtype=int64), array([1, 2, 0, 1, 0, 2], dtype=int64)) [[0 1] [0 2] [1 0] [1 1] [2 0] [2 2]]
# nonzero 함수는 True나 False 같은 boolean 값에 대해 False를 0으로 간주하기 떄문에 조건을 만족하는 인덱스를 찾을 수 있습니다.
print(a)
print(np.nonzero(a>15)) # True값들의 인덱스만 반환 ( False는 0으로 간주하기 때문 )
print(np.where(a>15))
[10 11 12 13 14 15 16 17 18 19] (array([6, 7, 8, 9], dtype=int64),) (array([6, 7, 8, 9], dtype=int64),)
# Numpy 배열의 모든 요소가 참 또는 거짓인지 확인
t = np.array([True, True, True])
print(np.all(t))
t[1] = False
print(t)
print(np.all(t))
True [ True False True] False
# all함수에 축인자를 지정하면 해당 축을 기준으로 True를 만족하는지를 반환
tt = np.array([[True, True], [False, True], [True, True]])
print(tt)
print(np.all(tt, 0)) # 각 0번째 인덱스가 모두 True인지
print(np.all(tt, 1))
[[ True True] [False True] [ True True]] [False True] [ True False True]
# numpy.all(), numpy.where() 함수를 이용하면 2개의 배열이 같은지 다른지를 찾을 수 있습니다.
a = np.arange(10)
b = np.arange(10)
print(a==b)
print(np.all(a==b)) # a, b배열의 모든 값이 같은지를 물어봅니다.
b[5] = -1
print(np.all(a==b))
print(np.where(a==b)) # a==b인 인덱스
print(np.where(a!=b)) # a!=b인 인덱스
[ True True True True True True True True True True] True False (array([0, 1, 2, 3, 4, 6, 7, 8, 9], dtype=int64),) (array([5], dtype=int64),)
이미지 작업에서는 이전 프레임과 다음 프레임간의 픽셀 값의 변화가 있는지, 변화가 있는 픽셀의 위치가 어디인지를 찾는 방법으로 움직임을 감지하거나 객체 추적과 같은 작업을 하는 데 이 함수들을 사용합니다.
3.1.13 기초 통계 함수¶
배열의 값이 하나하나를 확인할 수 없을만큼 많을 때는 평균, 최대 값, 최소 값 같은 통계 값들이 의미있는 정보가 됩니다.
- numpy.sum(array, [,axis]) : 배열의 합계
- numpy.mean(array, [,axis]) : 배열의 평균
- numpy.amin(array, [,axis]) : 배열의 최소 값
- numpy.min(array, [,axis]) : 배열의 최소 값
- numpy.amax(array, [,axis]) : 배열의 최대 값
- numpy.max(array, [,axis]) : 배열의 최대값
- array : 계산의 대상 배열
- axis : 계산 기준 축, 생략하면 모든 요소를 대상
a = np.arange(12).reshape(3,4)
print(a)
print(np.sum(a))
print(np.sum(a,0))
print(np.sum(a,1))
print(np.mean(a))
print(np.mean(a,0))
print(np.mean(a,1))
print(np.amin(a))
print(np.amin(a,0))
print(np.amin(a,1))
print(np.amax(a))
print(np.amax(a,0))
print(np.amax(a,1))
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] 66 [12 15 18 21] [ 6 22 38] 5.5 [4. 5. 6. 7.] [1.5 5.5 9.5] 0 [0 1 2 3] [0 4 8] 11 [ 8 9 10 11] [ 3 7 11]
3.1.14. 이미지 생성
import cv2
import numpy as np
img = np.zeros((120,120), dtype=np.uint8) # 120x120 2차원 배열 생성, 검은색 흑백 이미지
img[25:35, :] = 45 # 25~35행 모든 열에 45 할당
img[55:65, :] = 115 # 55~65행 모든 열에 115 할당
img[85:95, :] = 160 # 85~95행 모든 열에 160 할당
img[:, 35:45] = 205 # 모든행 35~45 열에 205 할당
img[:, 75:85] = 255 # 모든행 75~85 열에 255 할당
cv2.imshow('Gray', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV에서 이미지를 표현하기 위한 Numpy 배열은 반드시 dtype이 uint8이어야 합니다. ( 0 : 검은색, 255 : 흰색 )
img = np.zeros((120,120, 3), dtype=np.uint8) # 120x120 2차원 배열 생성, 3채널 컬러 이미지
img[25:35, :] = [255,0,0] # 25~35행 모든 열에 [255,0,0], 파랑색 할당
img[55:65, :] = [0, 255, 0] # 55~65행 모든 열에 [0,255,0], 초록색 할당
img[85:95, :] = [0,0,255] # 85~95행 모든 열에 [0,0,255], 빨강색 할당
img[:, 35:45] = [255,255,0] # 모든행 35~45 열에 [255,255,0], 하늘색 할당
img[:, 75:85] = [255,0,255] # 모든행 75~85 열에 [255,0,255], 분홍색 할당
cv2.imshow('BGR', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.2 Matplotlib¶
Matplotlib은 파이썬에서 가장 인기 있는 데이터 시각화 라이브러리로 도표나 차트 등을 손쉽게 그릴 수 있습니다. cv2.imshow() 함수를 여러번 호출하면 창이 여러개 열리므로 한 화면에 여러 이미지를 띄우려는 단순한 이유로 Matplotlib을 사용합니다.
Matplotlib 시각화 예제 : https://matplotlib.org/stable/gallery/index.html
3.2.1. Matplotlib¶
import matplotlib.pyplot as plt
3.2.2. plot¶
import numpy as np
a = np.array([2,6,7,3,12,8,4,5]) # x축 index는 자동 생성됩니다.
plt.plot(a)
plt.show()
x = np.arange(10)
y = x**2
plt.plot(x,y)
plt.show()
3.2.3. color와 style¶
색상기호
- b : blue
- g : green
- r : red
- c : cyan ( 청록색 )
- m : magenta ( 자홍색 )
- y : yellow
- k : black
- w : white
스타일 기호
- - 실선 ( 기본값 )
- -- 이음선
- -. 점 이음선
- : 점선
- . 점
- , 픽셀
- o 원
- v 역삼각형
- ^ 정삼각형
- < 좌삼각형
- > 우삼각형
- 1 작은 역삼각형
- 2 작은 정삼각형
- 3 작은 좌삼각형
- 4 작은 우삼각형
- s 사각형
- p 오각형
- * 별표
- h 육각형
- + 더하기 표
- D 다이아몬드 표
- X 엑스 표
x = np.arange(10)
y = x**2
plt.plot(x,y,'r')
plt.show()
x = np.arange(10)
f1 = x * 5
f2 = x **2
f3 = x **2 + x*2
plt.plot(x,'r--') # 빨강색 이음선
plt.plot(f1, 'g.') # 초록색 점
plt.plot(f2, 'bv') # 파랑색 역 삼각형
plt.plot(f3, 'ks' ) # 검정색 사각형
plt.show()
3.2.4. subplot¶
각각의 그래프를 분리해서 따로 그려야 할 때 사용한다
x = np.arange(10)
plt.subplot(2,2,1) #2행 2열 중에 1번째
plt.plot(x,x**2)
plt.subplot(2,2,2) #2행 2열 중에 2번째
plt.plot(x,x*5)
plt.subplot(223) #2행 2열 중에 3번째
plt.plot(x, np.sin(x))
plt.subplot(224) #2행 2열 중에 4번째
plt.plot(x,np.cos(x))
plt.show()
3.2.5. 이미지 표시¶
plt.plot() 대신에 plt.imshow() 함수를 호출하면 OpenCV로 읽어들인 이미지를 그래프 영역에 출력할 수 있습니다.
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('../img/girl.jpg')
plt.imshow(img) # 이미지 표시
plt.show()
# plt.imshow() 함수는 컬러 이미지를 RGB 순으로 해석하지만 OpenCV는 BGR 순으로 인식합니다.
img = cv2.imread('../img/girl.jpg')
plt.imshow(img[:,:,::-1]) # 이미지 컬러 채널을 뒤집어서 표시 ( 3번째가 채널 )
# plt.imshow(img[:,:,(2,1,0)]) 위와 같은 결과
plt.xticks([])
plt.yticks([])
plt.show()
img1 = cv2.imread('../img/woman1.jpg')
img2 = cv2.imread('../img/woman2.jpg')
img3 = cv2.imread('../img/woman3.jpg')
plt.subplot(1,3,1) #1행 3열 중에 1번째
plt.imshow(img1[:,:,(2,1,0)])
plt.xticks([]); plt.yticks([])
plt.subplot(1,3,2) #1행 3열 중에 2번째
plt.imshow(img2[:,:,(2,1,0)])
plt.xticks([]); plt.yticks([])
plt.subplot(1,3,3) #1행 3열 중에 3번째
plt.imshow(img3[:,:,(2,1,0)])
plt.xticks([]); plt.yticks([])
plt.show()
[마무리]¶
- 처음으로는 OpenCV를 사용하기 위해 필요한 NumPy에 대해 알아보았습니다.
- 다음으로는 NumPy 배열을 만드는 방법과 사용되는 함수에 대해 알아보았습니다.
- 마지막으로는 OpenCV에 사용되는 Matplotlib 라이브러리에 대해 알아보았습니다.
- 지금까지 본격적으로 OpenCV를 공부하기 전 기본적으로 알아야할 NumPy와 Matplotlib에 대해 알아보았습니다.
- 다음 시간에는 OpenCV 개념 및 기본 입출력 방식들에 대해 알아보겠습니다.
감사합니다 :)
'대외활동 > DACrew 2기' 카테고리의 다른 글
[ 파이썬으로 만드는 OpenCV 프로젝트🔥] 5장. 기하학적 변환 (0) | 2022.05.03 |
---|---|
[ 파이썬으로 만드는 OpenCV 프로젝트🔥] 4장. 이미지 프로세싱 기초 (1) | 2022.04.28 |
[ 파이썬으로 만드는 OpenCV 프로젝트🔥] 2장. 기본 입출력 (0) | 2022.04.22 |
댓글