[OpenCV] NumPy

rlath·2022년 4월 22일
0

OpenCV

목록 보기
16/17

이미지와 NumPy

OpenCV에서 이미지나 동영상을 읽어들이는 함수 cv2.imread() 는 NumPy 배열을 반환한다. 따라서 OpenCV를 파이썬으로 프로그래밍한다는 것은 NumPy 배열을 다룬다는 것과 같다. 그만큼 NumPy에 대해 잘 알면 OpenCV를 사용하는게 쉽다는 뜻이다.

NumPy 배열의 기본 속성

  • ndim : 차원(축)의 수
  • shape : 각 차원의 크기(튜플)
  • size : 전체 요소의 개수, shape 각 항목의 곱
  • dtype : 요소의 데이터 타입
  • itemsize : 각 요소의 바이트 크기

NumPy.ndarray

이미지는 여러 개의 픽셀들의 값으로 구성되므로 수많은 행과 열로 구성된 픽셀 데이터의 모음이다. 이 픽셀 데이터들을 프로그래밍 영역에서 다루기 위해서는 적절한 자료구조가 필요하다. OpenCV-Python은 예전에 독자적인 자료구조를 사용했는데 버전 2부터는 NumPy 라이브러리의 ndarray(N-Dimensional Array)를 가져다 쓰고 있다.

>>> import cv2
>>> img = cv2.imread('./test.jsp')
>>> type(img)
<class 'numpy.ndarray'>

ndarray

N-Dimensional Array의 약자로 N차원 배열, 즉 다차원 배열을 의미한다. OpenCV는 기본적으로 이미지를 3차원 배열, 행 X 열 X 채널 로 표현한다. 행과 열은 이미지의 크기의 길이를 갖고 채널은 컬러인 경우 RGB 3개의 길이를 갖는다. 따라서 일반적인 이미지를 읽었을 때 3차원 배열은 높이 X 폭 X 3 의 형태이다.

>>> img.ndim
3
>>> img.shape
(500, 500, 3)
>>> img.size
750000

NumPy 배열 생성

NumPy 배열을 만드는 방법에는 값을 가지고 생성하는 방법과 크기만 지정해서 생성하는 방법이 있다. 크기만 지정해서 생성하는 방법은 다시 특정한 초기 값을 모든 요소에 지정하는 경우와 값의 범위를 지정해서 순차적으로 증가 또는 감소하는 값을 갖게 하는 경우로 나뉜다.

  • 값으로 생성 : array()
  • 초기 값으로 생성 : empty(), zeros(), ones(), full()
  • 기존 배열로 생성 : empty_like(), zeros_like(), ones_like(), full_like()
  • 순차적인 값으로 생성: arrange()
  • 난수로 생성 : random.rand(), random.randn()

값으로 생성

배열 생성에 사용할 값을 가지고 있는 경우에는 numpy.array() 함수로 생성할 수 있다.

  • numpy.array(list, [dtype]) : 지정한 값들로 NumPy 배열 생성
    • list : 배열 생성에 사용할 값을 갖는 파이썬 리스트 객체
    • dtype : 데이터 타입(생략하면 리스트 항목 값에 의해 자동 결정)
      • int8, int16, int32, int64 : 부호 있는 정수
      • uint8, uint 16, uint32, uint64 : 부호 없는 정수
      • float16, float32, float64, float128 : 부동 소수점을 갖는 실수
      • complex64, complex128, complex256 : 부동 소수점을 갖는 복소수
      • bool : boolean(true, false)
# 1차원 배열 생성
>>> import numpy as np
>>> a = np.array([1, 2, 3, 4])
>>> a
array([1, 2, 3, 4])
>>> a.dtype
dtype('int64')
>>> a.shape
(4,)

# 2차원 배열 생성
>>> b = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
>>> b
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])
>>> b.shape
(2, 4)

크기와 초기 값으로 생성

초기 값으로 배열 생성

  • numpy.empty(shape, [dtype]) : 초기화되지 않은 값(쓰레기 값)으로 배열 생성
    • shape : 튜플, 배열의 각 차수의 크기 지정
  • numpy.zeros(shape, [dtype]) : 0으로 초기화된 배열 생성
  • numpy.ones(shape, [dtype]) : 1로 초기화된 배열 생성
  • numpy.full(shape, fill_value, [dtype]) : fill_value로 초기화된 배열 생성

기존에 있던 배열과 같은 크기의 배열 생성

  • empty_like(array, [dtype]) : 초기화되지 않은, array와 같은 shape과 dtype으로 배열 생성
  • zeros_like(array, [dtype]) : 0으로 초기화된, array와 같은 shape과 dtype으로 배열 생성
  • ones_like(array, [dtype]) : 1로 초기화된, array와 같은 shape과 dtype으로 배열 생성
  • full_like(array, fill_value, [dtype]) : fill_value로 초기화된, array와 같은 shape과 dtype으로 배열 생성

시퀀스와 난수로 생성

  • numpy.arrange([start=0], stop, [step=1, dtype=float64]) : 순차적인 값으로 생성
    • start : 시작 값
    • stop : 종료 값, 범위에 포함하는 수는 stop-1 까지
    • step : 증가 값
    • dtype : 데이터 타입

  • numpy.random.rand([d0, d1, dn]) : 0과 1 사이의 무작위 수로 생성
    • d0, d1...dn : shape, 생략하면 난수 한 개 반환

  • numpy.random.randn([d0, d1, ..., dn]) : 표준정규 분포(평균:0, 분산:1)를 따르는 무작위 수로 생성
# 시퀀스로 생성
>>> a = np.arrange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a.dtype
dtype('int64')
>>> a.shape
(5,)
>>> b = np.arrange(5.0)
>>> b
array([0., 1., 2., 3., 4.])
>>> b.dtype
dtype('float64')
>>> c = np.arrange(3, 9, 2)
>>> c
array([3, 5, 7])

# 난수로 생성
>>> np.random.rand()
0.29534415433963335
>>> np.random.randn()
0.881280912289447
>>> a = np.random.rand(2, 3)
>>> a
array([[0.66166792, 0.03228477, 0.51420786],
       [0.48056368, 0.59934148, 0.26385163]])
>>> b = np.random.randn(2, 3)
array([[0.54229341, -1.40139248, 1.0294928],
       [3.0041478, 0.84068375, -0.24592442]])

dtype 변경

  • ndarray.astype(dtype)
    • 변경하고 싶은 dtype, 문자열 또는 dtype

  • numpy.unitXX(array) : array를 부호 없는 정수(uint) 타입으로 변경해서 반환
    • unitXX : nt16, unit32, unit64

  • numpy.intXX(array) : array를 부호 있는 정수(int) 타입으로 변경해서 반환
    • intXX : int8, int16, int32, int64

  • numpy.floatXX(array) : array를 float 타입으로 변경해서 반환
    • floatXX : float16, float32, float64, float128

  • numpy.complexXX(array) : array를 복소수(complex) 타입으로 변경해서 반환
    • complexXX : complex64, complex 128, complex256
>>> a=arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = a.astype('float32')
>>> b
array([0., 1., 2., 3., 4.], dtype=float32)
>>> c = a.astype(np.float64)
>>> c.dtype
dtype('float64')

차원 변경

원래는 1차원이던 배열을 2행 3열 배열로 바꾼다든지, 100 X 200 X 3인 배열을 1차원으로 바꾸는 식의 작업이 필요할 때 사용할 수 있는 함수는 다음과 같다.

  • ndarray.reshape(newshape) : ndarray의 shape을 newshape으로 차원 변경
  • numpy.reshape(ndarray, newshape) : ndarray의 shape을 newshape으로 차원 변경
    • ndarray : 원본 배열 객체
    • newshape : 변경하고자 하는 새로운 shape(튜플)
  • numpy.ravel(ndarray) : 1차원 배열로 차원 변경
    • ndarray : 변경할 원본 배열
  • ndarray.T : 전치 배열(transpose), 행과 열을 서로 바꾸는 것
>>> a = np.arange(6)
>>> a
array(0, 1, 2, 3, 4, 5])
>>> b = a.reshape(2, 3)
>>> b
array([[0, 1, 2],
       [3, 4, 5]])
>>> c = np.reshape(a, (2, 3))
>>> c
array([[0, 1, 2],
       [3, 4, 5]])
       
       
# 전치 배열
>>> g = np.arange(10).reshape(2, -1)
>>> g
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
>>> g.T
array([[0, 5],
       [1, 6],
       [2, 7],
       [3, 8],
       [4, 9]])

브로드캐스팅 연산

원래 파이썬 리스트의 모든 항목 값을 1씩 증가시키기 위해선 반복문을 사용해야 한다.

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for i in range(len(l)):
...		l[i] += 1
>>> l
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

하지만 NumPy 배열에 +1 연산을 한 번만 해도 같은 결과를 얻게 된다. 이것을 브로드 캐스팅 연산이라고 한다.

>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a + 1
>>> a
array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

브로드캐스팅 연산은 더하기뿐만 아니라 모든 산술 연산이 가능하다. 그리고 산술 연산뿐만 아니라 비교 연산도 가능하다. 비교 연산의 결과는 각 항목에 대해 만족 여부를 bool 값으로 갖는 동일한 크기의 배열로 반환한다.

>>> a
array([0, 1, 2, 3, 4])
>>> a > 2
array([False, False, False, True, True])

>>> a = np. arange(10, 60, 10)
>>> b = np.arange(1, 6)
>>> a + b
array(11, 22, 33, 44, 55])
>>> a - b
array([9, 18, 27, 36, 45])

배열 간의 연산도 가능하지만 약간의 제약이 있다. 두 배열의 shape가 완전히 동일하거나 둘 중 하나가 1차원이면서 1차원 배열의 축의 길이가 같아야 한다.


profile
백엔드 개발자

0개의 댓글