Frameworks/OpenCV

[OpenCV] TypeError: Expected cv::UMat for argument

Jonghyuk Baek 2021. 12. 31. 02:27

Introduction

Python에서 OpenCV를 이용하다 보면 아래와 같은 에러 메시지를 분명히 본 적이 있을 것이다.

TypeError: Expected cv::UMat for argument [...]
TypeError: Expected Ptr<cv::UMat> for argument [...]

벌써부터 화가 난다...

말해주는 바는 명확하다. argument 뒤에 오는 인자의 타입으로 cv::UMat을 기대했지만 실제로는 아니라는 것이다. 하지만 우리가 일반적으로 파이썬 버전의 OpenCV에서 사용하는 데이터 타입은 numpy.ndarray 이다.

이 시점부터 혼란이 시작된다. numpy.ndarray에 어떤 조건이 더 걸려야 cv::UMat으로 타입 인식이 되는 걸까? 조건을 잘 만족시킨 것 같은데 왜 계속 막히는 걸까? 이렇게 골머리를 앓다 보면 30분이고 한시간이고 훌쩍 지나 있기 마련이다.

오늘은 opencv-python의 cv::UMat 타입에 대해 알아보고 위 에러의 해결책에 대해 알아볼 생각이다.
(문제 해결이 급한 사람은 아래의 what is the solution 항목으로 스킵하도록 하자)

 


What is cv::UMat ?

cv::UMat 이란게 뭐길래 이렇게 발목을 잡는 걸까?

일단 생긴 것에서 유추할 수 있듯이 이건 파이썬 버전으로 wrap 된 OpenCV에서가 아니라, 그 소스인 C++ 기반의 OpenCV 라이브러리에서 등장하는 클래스이다.

UMat 클래스는 OpenCV의 함수들에게, OpenCL (Open Computing Language) 이 활성화된 GPU를 사용하는 코드로 이미지를 처리를 가속하라고 알려주는 역할을 한다. 만약 사용 가능한 GPU가 없다면 함수 실행에 CPU를 대신 이용하게 된다.

OpenCV 3.0 버전에서 T-API (Transparent API) 라는 것이 도입되면서 100개 가량의 OpenCV 함수에 대해 이 OpenCV availability를 자동으로 탐지하고 이용할 수 있게 바뀌었는데, 이로 인해 사용자는 OpenCL 이용과 관련된 코드를 따로 직접 작성할 필요 없이, 기존에 사용되던 Mat 클래스를 UMat으로 대체해 작성하기만 하면 됐다.

 

// OpenCL-aware code OpenCV-2.x
// initialization
VideoCapture vcap(...);
ocl::OclCascadeClassifier fd("haar_ff.xml");
ocl::oclMat frame, frameGray;
Mat frameCpu;
vector<rect> faces;
for(;;){
  // processing loop
  vcap >> frameCpu;
  frame = frameCpu;
  ocl::cvtColor(frame, frameGray, BGR2GRAY);
  ocl::equalizeHist(frameGray, frameGray);
  fd.detectMultiScale(frameGray, faces, ...);
  // draw rectangles …
  // show image …
}

OpenCL 이용을 위해 OpenCL-aware 코드를 작성해야 했던 OpenCV 2 버전

// OpenCL-aware code OpenCV-3.x
// initialization
VideoCapture vcap(...);
CascadeClassifier fd("haar_ff.xml");
UMat frame, frameGray;
vector<rect> faces;
for(;;){
  // processing loop
  vcap >> frame;
  cvtColor(frame, frameGray, BGR2GRAY);
  equalizeHist(frameGray, frameGray);
  fd.detectMultiScale(frameGray, faces, ...);
  // draw rectangles …
  // show image …
}

OpenCV 3부터는 T-API의 도입으로 OpenCL 이용을 위한 코드를 따로 추가할 필요가 없어졌다
출처: https://docs.opencv.org/3.0-rc1/db/dfa/tutorial_transition_guide.html#tutorial_transition_hints_opencl

 

https://jeanvitor.com/opencv-opencl-umat-performance/

위는 OpenCL을 이용해 연산이 가속되었을 때와 아닐 때의 성능 비교 그래프로, 확실한 시간 이득을 보이는 걸 확인할 수 있다.

결국 정리해 보자면, cv::UMatcv::Mat 에서 더 나아간 형태로, 현재 시스템에서 OpenCL이 available 하다면 CPU 대신 GPU를 이용해 함수 연산을 가속하도록 코드 변경 없는 스무스한 전환을 가능하게 해주는 데이터 타입이라 보면 된다.

그럼... 문제를 파악하기 위해서 이제는 cv::Mat 을 한번 다시 뜯어봐야 할 차례일까?

사실 그러지 않아도 된다. 열심히 설명한 것이 무색하게도 파이썬 wrapper 버전인 opencv-python에서는 모든 작업이 numpy array 상에서 이루어지며, 일반적인 상황에서는 c++의 Mat / UMat 오브젝트와 파이썬의 numpy array 사이의 변환 과정은 사용자가 전혀 신경 쓸 필요가 없다는 것이 중론이다. (아예 이런 것들이 존재한다는 것을 알 필요도 없다!)

 

So.. what is the solution?

그런데 사용자는 numpy array만 잘 이용하면 되도록 짜 놓았다는 것과 반대로 우리의 프로그램에서는 'Expected cv::UMat for argument' 같은 에러 메시지를 빡빡 뱉어내니 어이가 없을 수 밖에 없다.

그럼 한번 실질적인 해결책들을 짚어보도록 하자.

 

1. numpy.ndarray 가 아닌 경우

가끔씩 contour 같이 hierarchy를 가진 어레이를 다루어야 하는 경우, 개별 알고리즘 수행을 통해 얻어낸 어레이를 서로 묶었다 풀었다 해야 하는 경우가 많다. (마스크 상의 모든 contour 대해 특정한 처리를 한 후 다시 마스크로 변환해야 한다던지)

이 과정에서 서로 다른 포인트나 컨투어를 한 데 묶는 데에 list를 이용해 append 하는 경우가 종종 있는데, 이렇게 묶인 새 데이터를 다음번 contour 관련 OpenCV 함수에 전달해 줄 때 numpy array 데이터 타입을 씌우지 않는 경우 해당 에러를 볼 수 있다.

물론 예시를 contour로 들었지만, src나 img, dst 등 numpy array를 필요로 하는 함수 인자라면 어디에서든 이런 케이스가 발생할 수 있다.

# Solution 1. 
image = np.array(image)

솔루션이 싱겁다..

가끔 type를 찍어서 <class 'numpy.ndarray'> 가 나오는 경우에도 np.array 캐스팅이 해결책이 된다는 리포트도 있다. 한 번쯤 부담없이 시도해 볼 만하다.

 

2. OpenCV function에서 지원하는 dtype이 아닌 경우

그런데 분명 타입이 numpy array임에도 불구하고 똑같은 에러가 뜰 수도 있다. 그럴 때는 호출하는 함수가 지원하는 데이터형이 어떤 것인지를 파악해 봐야 한다.

OpenCV의 각 함수 별로 지원하는 numpy array의 데이터형이 다르기 때문에, 만약 지원되지 않는 데이터 타입을 가진 어레이를 넣어주게 되면 다시 같은 에러 메시지를 보게 된다.

>>> import cv2 
>>> import numpy as np 
>>> img = np.ones((5, 5, 3)) 
>>> img = img.astype(np.uint32) 
>>> img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 

Traceback (most recent call last): File "<stdin>", line 1, in <module> 
TypeError: Expected Ptr<cv::UMat> for argument 'src' 

>>> # Solution 2. 
>>> img = img.astype(np.uint8) 
>>> img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 
>>>

위는 cv2.cvtColornp.uint32 데이터형을 지원하지 않아서 생기는 현상이다. 에러 메시지가 좀 더 informative 했다면 얼마나 좋을까 생각이 든다.

 

3. 정 안 된다면...

그런데 가끔씩 이런 경우가 있다. numpy array도 맞고 데이터형도 분명히 맞춰 놨는데 자꾸 에러가 나는 경우이다. 물론 위 두 조건이 잘 체크가 안 되었을 가능성도 있지만, 실제로 이런 경우는 분명히 발생한다 (꽤 종종 발생하는 것 같다...).

 

이런 케이스는 모종의 이유로 파이썬 wrapper 단에서 numpy.ndarray -> cv::UMat으로의 변환이 실패한 경우이다. 다행히 이럴 때 작동하는 (작동한다고 알려진) 방법들이 몇 개 있다. 정 진행이 막히고 빨리 결과를 봐야 하는 상황이라면 시도해 볼 만 하다.

# Solution 3-1. 
img = img.copy()
# Solution 3-2. 
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) 
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Solution 3-3. 
img = np.ascontiguousarray(img)
# Solution 3-X. 
# 뭔가 엉켰다면 다시 새로 읽어오는 것도 방법이다.. 
img = cv2.imread(...)

 


본인의 삽질 반복을 방지하고 삽질 내용도 정리해볼 겸 적어 보았다. 도움이 되었으면 좋겠다.

아래는 참고할 만한 사이트이다.

References

OpenCV 3.0 Transition guide
https://docs.opencv.org/3.0-rc1/db/dfa/tutorial_transition_guide.html#tutorial_transition_hints_opencl
OpenCV 3.0 Documentation
https://opencv.org/opencv-3-0/
Creating Mat with openCV in python
https://stackoverflow.com/questions/37182774/creating-mat-with-opencv-in-python
A little about OpenCV’s UMat class
https://jeanvitor.com/opencv-opencl-umat-performance/
BUG:TypeError: Expected Ptr<cv::UMat> for argument 'img' #18120
https://github.com/opencv/opencv/issues/18120
OpenCL | NVIDIA Developer
https://developer.nvidia.com/opencl

반응형