본문 바로가기

프로그래밍 강좌/C++ - OpenCV

[OpenCV][C++] cv::Mat 클래스 총정리(4) - OpenCL과 cv::UMat 사용하기 OpenCL OpenMP

cv::Mat 클래스와 관련되어 벌써 4번째 시리즈네요.

그만큼 영상처리에 많이 사용되면서도 가장 중요한 OpenCV의 클래스지요.

지난번에 Mat 클래스에 빠르게 원소 접근하는 방법에 대해 알아 봤습니다.

이번에는 UMat 클래스에 대해 알아보겠습니다.

UMat 클래스란?

UMat 클래스는 Mat 클래스와 동일한 방법으로 OpenCV의 모든 함수에서 사용 가능하며, OpenCV 함수를 사용할 때 OpenCL(Open Computing Language)이 활성화된 GPU를 사용하는 코드로 영상 처리를 가속하라고 알려주는 역할을 합니다. 만약 사용 가능한 GPU가 없다면 함수 실행에 CPU를 대신 이용합니다.

이전에는 OpenCL을 사용할 때 OpenCL 관련된 코드를 따로 작성했어야 하는데, 이제는 cv::Mat 클래스를 cv::UMat 클래스로 대체해서 작성하기만 하면 됩니다. UMat 을 사용하기만 하면 아래와 같이 속도가 빨라진다고 하니, 한번 살펴보도록 하겠습니다.

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

1. Mat 과 UMat 테스트 함수 생성

cv::Mat 과 cv::UMat 영상을 받는 임의 함수들을 만듭니다.

두 함수는 cv::Mat 과 cv::UMat 만 다를 뿐 나머지 변수, 함수들은 같아야 합니다.

void cvMatTest(cv::Mat mat) {
	cv::Mat dst;	
	cv::blur(mat, dst, cv::Size(21, 21));
	cv::Mat rst;
	cv::subtract(mat, dst, rst);	
	cv::Mat median;
	cv::medianBlur(rst, median, 5);
	cv::Mat bilateral;
	int d = 1;
	double sigmaColor = 10.;
	double sigmaSpace = 5.;
	cv::bilateralFilter(mat, bilateral, d, sigmaColor, sigmaSpace);
	
}

void cvUMatTest(cv::UMat umat) {
	cv::UMat dst = cv::UMat(cv::Size(umat.cols, umat.rows), umat.type(), cv::USAGE_DEFAULT);	
	cv::blur(umat, dst, cv::Size(21, 21));
	cv::UMat rst;
	cv::subtract(umat, dst, rst);
	cv::UMat median;
	cv::medianBlur(rst, median, 5);
	cv::UMat bilateral;
	int d = 1;
	double sigmaColor = 10.;
	double sigmaSpace = 5.;
	cv::bilateralFilter(umat, bilateral, d, sigmaColor, sigmaSpace);
	
}
 

cv::UMat을 직접 읽어오는 함수는 없습니다. cv::Mat을 반환하는 imread()를 이용해서 읽어온 후 getUMat()을 사용하든지 copyTo()를 사용하면 됩니다.

cv::TickMeter tt, tt1, tt2, tt3;
// dummy 실행
tt.start();
cv::UMat uSrc_20;
cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE).copyTo(uSrc_20);
tt.stop();

tt1.start();
cv::Mat src = cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE);	
tt1.stop();	
tt2.start();
cv::UMat uSrc_1 = cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE).getUMat(cv::ACCESS_READ);
tt2.stop();
tt3.start();
cv::UMat uSrc_2;
cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE).copyTo(uSrc_2);
tt3.stop();	
std::cout << "imread: (" << tt1.getTimeMilli() << "ms," << tt2.getTimeMilli() << "ms," 
		<< tt3.getTimeMilli() << "ms)" << std::endl;
 

결과는 아래와 같습니다. UMat을 처음 사용할 때 코어 할당등으로 정확한 속도 비교를 할 수 없으니 dummy로 UMat을 한번 불러주고 시작합니다.

절대 속도는 실행할 때마다 컴퓨터의 상태와 구성, 영상 사이즈에 따라 다를 수 있습니다.

결과는 아래와 같습니다.

imread를 한 후 getUMat() 또는 copyTo()를 하니까 그 함수를 부르는 시간이 더 걸리게 됩니다. getUMat()이 copyTo() 보다는 빠르네요.

tm1.start();
cv::Mat src_clone = src.clone();
tm1.stop();
tm2.start();
cv::UMat uSrc_clone = uSrc_1.clone();
tm2.stop();		

tm3.start();
cv::Mat src_copyTo;
src.copyTo(src_copyTo);
tm3.stop();
tm4.start();
cv::UMat uSrc_copyTo;
uSrc_1.copyTo(uSrc_copyTo);
tm4.stop();		
 

결과는 아래와 같습니다.

같은 타입으로의 deep copy를 하는 것은 cv::Mat 보다 cv::UMat이 빠르네요.

 

4. 타입 변환(Mat ↔ UMat)

타입을 변환하는 방법은 getMat(), getUMat() 이 있고, copyTo()도 가능합니다.

tm5.start();
cv::UMat src_umat = src_clone.getUMat(cv::ACCESS_RW, cv::USAGE_ALLOCATE_SHARED_MEMORY);
tm5.stop();
tm6.start();
cv::Mat src_mat = uSrc_clone.getMat(cv::ACCESS_RW);
tm6.stop();		

tm7.start();
cv::UMat src_umat2;
cv::copyTo(src_clone, src_umat2, cv::noArray());
tm7.stop();
tm8.start();
cv::Mat src_mat2;
cv::copyTo(uSrc_clone, src_mat2, cv::noArray());
tm8.stop();		

tm9.start();
cv::UMat src_umat3;
src_clone.copyTo(src_umat3);
tm9.stop();
tm10.start();
cv::Mat src_mat3;
uSrc_clone.copyTo(src_mat3);
tm10.stop();
 

cv::Mat → cv::UMat 변환  getUMat()이 가장 빠르고, cv::Mat → cv::Mat 변환  copyTo()가 가장 빠르네요.

5. OpenCV 함수 실행 속도 측정

UMat을 사용할 때 OpenCL이 자동으로 동작하는지 확인하기 위해 처음에는 setUseOpenCL을 부르지 않고 실행을 하고,

두번째에는 setUseOpenCL을 부른 후 실행을 합니다. 그리고 마지막에는 setUseOpenCL을 끄고 실행을 해볼께요.

cv::TickMeter tk1, tk2, tk3, tk4, tk5, tk6;	
tk1.start();
for(int i = 0; i< nRepeat; i++)
	cvMatTest(src);
tk1.stop();

tk2.start();
for(int i = 0; i < nRepeat; i++)
	cvUMatTest(uSrc_1);
tk2.stop();
std::cout << "Test: (" << tk1.getTimeMilli()/nRepeat << "ms," 
          << tk2.getTimeMilli()/nRepeat << "ms)" << std::endl;
		
cv::ocl::setUseOpenCL(true);
tk3.start();
for(int i = 0; i < nRepeat; i++)
	cvMatTest(src);
tk3.stop();	

tk4.start();
for (int i = 0; i < nRepeat; i++)
	cvUMatTest(uSrc_1);
tk4.stop();
std::cout << "Test_par: (" << tk3.getTimeMilli() / nRepeat << "ms," 
          << tk4.getTimeMilli() / nRepeat << "ms)" << std::endl;	
	
cv::ocl::setUseOpenCL(false);
tk5.start();
for (int i = 0; i < nRepeat; ++i)
	cvMatTest(src);
tk5.stop();
tk6.start();
for (int i = 0; i < nRepeat; i++)
	cvUMatTest(uSrc_1);
tk6.stop();
std::cout << "UMatTestwoOpenCL: (" << tk5.getTimeMilli() / nRepeat << " ms, " 
          << tk6.getTimeMilli() / nRepeat << "ms)" << std::endl;
 
결과는 아래와 같습니다. cv::Mat 보다 cv::UMat이 4배 이상 빠른 것으로 보입니다. (여러 함수를 한번에 부른 결과이고, 함수마다 속도 차이는 다릅니다. 어떤 함수는 10배 이상 차이나는 것이 있는 반면에 어떤 함수는 2배 정도 빠른 함수가 있습니다. 직접 해 보세요.)
그리고 OpenCL 관련해서는 setUseOpenCL(true)를 부르나 부르기 전이나 Test 결과 속도는 유의차가 없는 것으로 측정되었습니다.
하지만 마지막 테스트에서처럼, setUseOpenCL(false)로 했는데, cv::Mat은 별 차이가 나지 않는데 반해 cv::UMat은 현저히 느려지는 것을 볼 수 있습니다.

이 실험의 전체 소스는 아래와 같습니다. 측정의 신빙성을 확보하기 위해

30회 반복 측정을 한 평균 속도를 비교하였습니다.

#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/core/ocl.hpp"
#include <Windows.h>

void cvMatTest(cv::Mat mat) {
	cv::Mat dst;	
	cv::blur(mat, dst, cv::Size(21, 21));
	cv::Mat rst;
	cv::subtract(mat, dst, rst);	
	cv::Mat median;
	cv::medianBlur(rst, median, 5);
	cv::Mat bilateral;
	int d = 1;
	double sigmaColor = 10.;
	double sigmaSpace = 5.;
	cv::bilateralFilter(mat, bilateral, d, sigmaColor, sigmaSpace);	
}

void cvUMatTest(cv::UMat umat) {
	cv::UMat dst = cv::UMat(cv::Size(umat.cols, umat.rows), umat.type(), cv::USAGE_DEFAULT);	
	cv::blur(umat, dst, cv::Size(21, 21));
	cv::UMat rst;
	cv::subtract(umat, dst, rst);
	cv::UMat median;
	cv::medianBlur(rst, median, 5);
	cv::UMat bilateral;
	int d = 1;
	double sigmaColor = 10.;
	double sigmaSpace = 5.;
	cv::bilateralFilter(umat, bilateral, d, sigmaColor, sigmaSpace);	
}

int main()
{
	int nRepeat = 30;
	//----------------------------------------------------------------
	cv::TickMeter tt, tt1, tt2, tt3;
	// dummy 실행
	tt.start();
	cv::UMat uSrc_20;
	cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE).copyTo(uSrc_20);
	tt.stop();

	tt1.start();
	cv::Mat src = cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE);	
	tt1.stop();	
	tt2.start();
	cv::UMat uSrc_1 = cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE).getUMat(cv::ACCESS_READ);
	tt2.stop();
	tt3.start();
	cv::UMat uSrc_2;
	cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE).copyTo(uSrc_2);
	tt3.stop();	
	std::cout << "imread: (" << tt1.getTimeMilli() << "ms," << tt2.getTimeMilli() << "ms," 
		<< tt3.getTimeMilli() << "ms)" << std::endl;

	//----------------------------------------------------------------
	cv::TickMeter tm1, tm2, tm3, tm4, tm5, tm6, tm7, tm8, tm9, tm10;
	for (int i = 0; i < nRepeat; ++i) {
		tm1.start();
		cv::Mat src_clone = src.clone();
		tm1.stop();
		tm2.start();
		cv::UMat uSrc_clone = uSrc_1.clone();
		tm2.stop();		

		tm3.start();
		cv::Mat src_copyTo;
		src.copyTo(src_copyTo);
		tm3.stop();
		tm4.start();
		cv::UMat uSrc_copyTo;
		uSrc_1.copyTo(uSrc_copyTo);
		tm4.stop();		

		tm5.start();
		cv::UMat src_umat = src_clone.getUMat(cv::ACCESS_RW, cv::USAGE_ALLOCATE_SHARED_MEMORY);
		tm5.stop();
		tm6.start();
		cv::Mat src_mat = uSrc_clone.getMat(cv::ACCESS_RW);
		tm6.stop();		

		tm7.start();
		cv::UMat src_umat2;
		cv::copyTo(src_clone, src_umat2, cv::noArray());
		tm7.stop();
		tm8.start();
		cv::Mat src_mat2;
		cv::copyTo(uSrc_clone, src_mat2, cv::noArray());
		tm8.stop();		

		tm9.start();
		cv::UMat src_umat3;
		src_clone.copyTo(src_umat3);
		tm9.stop();
		tm10.start();
		cv::Mat src_mat3;
		uSrc_clone.copyTo(src_mat3);
		tm10.stop();		
	}
	std::cout << "clone: (" << tm1.getAvgTimeMilli() << "ms," << tm2.getAvgTimeMilli() << "ms)" << std::endl;
	std::cout << "copyTo: (" << tm3.getAvgTimeMilli() << "ms," << tm4.getAvgTimeMilli() << "ms)" << std::endl;
	std::cout << "getUMat(mat -> umat): " << tm5.getAvgTimeMilli() << " ms" << std::endl;
	std::cout << "getMat(umat -> mat): " << tm6.getAvgTimeMilli() << " ms" << std::endl;
	std::cout << "copyTo(mat -> umat): " << tm7.getAvgTimeMilli() << " ms" << std::endl;
	std::cout << "copyTo(umat -> mat): " << tm8.getAvgTimeMilli() << " ms" << std::endl;
	std::cout << "copyTo_(mat -> umat): " << tm9.getAvgTimeMilli() << " ms" << std::endl;
	std::cout << "copyTo_(umat -> mat): " << tm10.getAvgTimeMilli() << " ms" << std::endl;
	//----------------------------------------------------------------
	cv::TickMeter tk1, tk2, tk3, tk4, tk5, tk6;	
	tk1.start();
	for(int i = 0; i< nRepeat; i++)
		cvMatTest(src);
	tk1.stop();

	tk2.start();
	for(int i = 0; i < nRepeat; i++)
		cvUMatTest(uSrc_1);
	tk2.stop();
	std::cout << "Test: (" << tk1.getTimeMilli()/nRepeat << "ms," << tk2.getTimeMilli()/nRepeat << "ms)" << std::endl;
		
	cv::ocl::setUseOpenCL(true);
	tk3.start();
	for(int i = 0; i < nRepeat; i++)
		cvMatTest(src);
	tk3.stop();	

	tk4.start();
	for (int i = 0; i < nRepeat; i++)
		cvUMatTest(uSrc_1);
	tk4.stop();
	std::cout << "Test_par: (" << tk3.getTimeMilli() / nRepeat << "ms," << tk4.getTimeMilli() / nRepeat << "ms)" << std::endl;	
	
	cv::ocl::setUseOpenCL(false);
	tk5.start();
	for (int i = 0; i < nRepeat; ++i)
		cvMatTest(src);
	tk5.stop();
	tk6.start();
	for (int i = 0; i < nRepeat; i++)
		cvUMatTest(uSrc_1);
	tk6.stop();
	std::cout << "UMatTestwoOpenCL: (" << tk5.getTimeMilli() / nRepeat << " ms, " << tk6.getTimeMilli() / nRepeat << "ms)" << std::endl;

    return 0;
}
 

오늘 실험의 결과는 아래와 같습니다.

1. cv::UMat은 별도의 OpenCL을 시작하는 명령어를 부르지 않아도 자동으로 OpenCL을 사용한다는 것을 확인함

2. cv::Mat 사용한 함수 보다는 cv::UMat 함수를 사용하면 수행 속도가 빠름

3. cv::Mat과 cv::UMat 간 변환에 약간의 시간이 걸리긴 하나 Process가 많을 경우(영상이 크거나 많은 함수를 부를 경우)에는 충분히 이득이 있음(getUMat() 사용하자)

 

Intel 에서 아래와 같이 권장했다고 하네요.

영상을 다룰 경우 cv::UMat을 사용하는 것을 권장하고,
convolution matrices 나 transpose matrices 같은
작은 데이터 구조를 다룰 때는 cv::Mat을 사용하는 것을 권장한다!