🐍Python/OpenCV

[OpenCV] 03-3. Performance Measurement and Improvement Techniques

728x90
반응형

<3-3. Performance Measurement and Improvement Techniques>

이미지 전처리과정에서, 초당 수많은 기능들을 다루기 때문에 코드가 정확한 해결책을 줄 뿐만 아니라, 빨라야하는게 필수적이다. 이번 장에서는 다음의 것들을 배울 것이다.

  • 코드의 성능을 측정
  • 코드의 성능을 향상시키는 조금의 방법
  • cv2.getTickCount(), cv2.getTickFrequency() 함수

OpenCV와 별개로, Python에서는 실행 시간을 측정할 수 있는 ‘time’ 모듈이 있다. 또 다른 모듈로는 ‘profile’로, 어떤 코드에 얼마나 시간이 걸렸는지, 함수가 몇 번 불렸는지등 자세하게 코드에 대한 리포트를 보여준다. 하지만 IPython을 사용중이라면, 이 모든 기능은 객체 지향적인 방식으로 통합된다. 중요한 몇 가지를 볼 것이고, 자세한 내용은 아래의 내용을 보면 된다!

Measuring Performance with OpenCV

cv2.getTickCount 함수는 기준 이벤트 후 (기계가 켜지는 것 처럼) clock-cycle를 함수가 불러진 후 반환한다. 그러니 실행하기 전 후로 사용할 수 있고, 사용한 함수의 clock-cycle 수를 얻을 수 있다.

cv2.getTickFrequency 함수는 clock-cycles의 빈도 혹은 초당 clock-cycle의 수를 반환한다. 그래서 실행 시간을 보려면 아래처럼 하면 된다!

import cv2
import numpy as np

e1 = cv2.getTickCount()
# e1 ~ e2 사이에 실행할 코드를 넣는다
e2 = cv2.getTickCount()
time = (e2-e1) / cv2.getTickFrequency()

아래의 코드 예를 보자. 다음의 예에서 홀수 크기의 커널을 사용하여 median filtering을 적용한다.

import cv2
import numpy as np

img1 = cv2.imread('golden.jpg')

e1 = cv2.getTickCount()
for i in range(5,49,2):
    img1 = cv2.medianBlur(img1,i)
e2 = cv2.getTickCount()
time = (e2-e1) / cv2.getTickFrequency()
print(time)
### 0.656860643초

cv2.getTickCount() 대신에 time.time()을 사용해도 된다.


Default Optimization in OpenCV

OpenCV의 많은 함수들은 SSE2,AVX 등을 사용하여 최적화한다. 최적화되지 않은 코드도 포함한다. 컴파일하는 동안 기본적으로 활성화된다. 그래서 OpenCV가 활성화되면 최적화된 코드를 실행하고, 그렇지 않으면 최적화되지 않은 코드를 실행한다. cv2.useOptimized()를 사용하여 활성화/비활성화되는지 확인하고cv2.setUseOptimized()를 사용하여 활성화/비활성화 할 수 있다. 간단한 예를 보자!

# 최적화가 되는지 체크
cv2.useOptimized()
>>> True
%timeit res = cv2.medianBlur(img1,49)
>>>12 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# 사용못하게
cv2.setUseOptimized(False)
cv2.useOptimized()
>>> False
%timeit res = cv2.medianBlur(img1,49)
>>> 12.2 ms ± 137 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# 최적화된 코드가 더 빠르다.

median filtering은 SIMD 최적화를 사용한다. 그래서 이를 코드 맨위에 작성해서 최적화를 사용할 수 있다.


Measuring Performance in IPython

가끔 두 개의 비슷한 기능의 성능을 비교할 필요가 있다. IPython은 %timeit이라는 마법의 커맨드를 제공하고 있다. 이는 정확한 결과를 얻기 위해 코드를 여러번 실행한다. 다시 말하지만, 이는 단 한 줄의 코드를 측정하는데 알맞다.

예를 들어서, 다음 중 4개 중에서 어떤 연산이 더 나은지 아는가?

x  =  5;  y  =  x**2, 
x  =  5;  y  =  x*x,
x  =  np.uint8([5]);  y  =  x*x 
#or
y  =  np.square(x)

이를 %timeit 으로 알아볼 것 이다.

x = 5
%timeit  y=x**2
>>> 228 ns ± 3.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit y = x*x
>>>42.5 ns ± 0.55 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

z = np.uint8([5])
%timeit y = z*z
>>>459 ns ± 9.87 ns per loop (mean ± std.  dev. of 7 runs, 1000000 loops each)

%timeit y=np.square(z)
>>> 433 ns ± 7.62 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

y=xx가 제일 빠른 것을 볼 수 있다. array 생성 속도에 대해서 최대 100배까지 높일 수 있다.

Python scalar 연산은 Numpy scalar 연산보다 빠르다. Numpy는 array의 크기가 클 경우 더 유용하다.

여러 샘플을 더 보자. 동일 이미지에 대해 cv2.countNonZero() 와 np.count_nonzero() 두 개를 비교해보자.

# 1 채널에서만 가능 = 흑백
img = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
%timeit z = cv2.countNonZero(img)
>>> 6.65 µs ± 134 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit z = np.count_nonzero(img)
>>> 287 µs ± 321 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

OpenCV가 더 빠른 모습을 보였다. 일반적으로 OpenCV 기능은 Numpy 기능보다 빠르다. 따라서 동일한 작동을 위해서는 OpenCV 기능이 선호된다. 그러나, 특히 Numpy가 복사본이 아닌 view로 작업할 때 예외가 있을 수 있다.

More IPython magic commands

profiling, line profiling, memory measurement 등 성능 측정을 위한 마법의 커맨드들이 여러 개 있다. 이들은 충분히 입증되어있다. 직접 시험해보는 것도 좋다!

Performance Optimization Techniques

Python과 Numpy의 최대 성능을 얻기 위해 사용하는 여러 기술과 코딩 방법이 있다. 주목해야할 것은, 우선 간단한 방법으로 알고리즘을 구현해 보라는 것이다. 일단 작동되면 프로파일링하고, 병목현상을 찾고 최적화하면 된다.

  1. Python에서 루프를 가급적 사용하지 말라! 특히 이중 / 삼중 루프는 원래 느리다.
  2. Numpy와 OpenCV는 벡터 연산에 최적화되어 있으므로 알고리즘/코드를 최대한 벡터화한다.
  3. 캐쉬 일관성(coherence)을 이용하라.
  4. 필요한 경우가 아니면 array를 복사하지 말라. 대신 views를 사용해라. array 복사는 비용이 많이 드는 작업이다.

이러한 모든 연산을 수행한 후에도, 코드가 아직까지 느리거나, 큰 루프를 사용해야 하는 경우, Cython과 같은 추가 라이브러리를 사용하여 더 빨리 만들어야 한다.

728x90
반응형