CUDA 활용하기

  • CUDA는 그래픽 처리장치(GPU)에서 수행하는 병령처리 알고리즘을 C 프로그래밍 언어를 비롯한 산업 표준 언어를 사용하여 작성할 수 있도록 하는 GPGPU(General-Purpose computing on Graphics Processing Units) 기술

CUDA를 Enable해서 OpenCV 빌드

  • CUDA Toolkit 설치 : https://developer.nvidia.com/cuda-downloads
    • C:\Program Files\NVIDIA GPU Computing Toolkit\ 디렉터리 아래에 설치됨
  • (Optional) CUDNN(쿠다로 딥러닝을 실행할 때) 설치 : https://developer.nvidia.com/rdp/udnn-download
    • C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.6\ 디렉토리에 압축을 해제
  • OpenCV 소스 코드 다운로드
    • https://github.com/opencv/opencv/releases 에서 OpenCV 메인 모듈 소스 코드 다운로드
    • https://github.com/opencv/opencv_contrib/tags 에서 OpenCV 추가 모듈 소스 코드 다운로드
    • C:\opencv 폴더를 만들고 OpenCV 메인 모듈과 추가 모듈 소스 코드의 압축을 해제
    • 빌드 작업을 위한 C:\opencv\build 폴더 생성
  • CMake 프로그램 설정
    • OpenCV 메인 모듈 소스 코드 및 빌드 폴더 설정
    • opencv_contrib 소스코드 다운로드 및 CMake에서 OPENCV_EXTRA_MODULES_PATH 설정
    • 옵션에서 WITH_CUDA 선택
    • (Optional) 옵션에서 WITH_CUDNN 선택
  • 시스템 환경 병수 설정
    • OPENCV_DIR 환경 변수를 C:\opencv\build\install로 설정
    • PATH 환경 변수에서 %OPENCV_DIR$\x64\vc17/bin 추가

CUDA를 이용한 Hough 선분 검출 예제 코드

#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/cudaimgproc.hpp"

using namespace std;
using namespace cv;
using namespace cv::cuda;

void houghLinesCPU(const Mat& src, Mat& dst);
void houghLinesGPU(const Mat& src, Mat& dst);

int main()
{
	// Print cuda device info.

	cout << "device count: " << getCudaEnabledDeviceCount() << endl; // CUDA를 사용할 수 있는 DEVICE의 개수를 출력

	int device_id = getDevice();
	cout << "device id: " << device_id << endl; // 현재 시스템에 설치되어있는 그래픽카드의 ID를 출력

	printShortCudaDeviceInfo(device_id); // 현재 시스템에 설치되어있는 NVIDIA 그래픽카드의 정보를 출력
	cout << endl;

	// Hough line detection

	Mat src = imread("building.jpg", IMREAD_GRAYSCALE); // 입력 영상을 불러옴

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	Mat dst_cpu, dst_gpu;

	houghLinesCPU(src, dst_cpu); // 영상을 CPU를 사용하는 houghlinesCPU 함수에 전달
	houghLinesGPU(src, dst_gpu); // 영상을 GPU를 사용하는 houghlinesGPU 함수에 전달

	imshow("dst_cpu", dst_cpu);
	imshow("dst_gpu", dst_gpu);
	waitKey();
}

void houghLinesCPU(const Mat& src, Mat& dst)
{
	int64 t1 = getTickCount();

	Mat edges;
	Canny(src, edges, 100, 200, 3); // 캐니 함수를 이용해서 엣지를 검출함

	vector<Vec4i> lines;
	HoughLinesP(edges, lines, 1, CV_PI / 180, 160, 50, 5); //허프라인P 함수를 이용해서 선분을 검출함
	
	int64 t2 = getTickCount();
	cout << "Canny & Hough (CPU version) took " << (t2 - t1) * 1000 / getTickFrequency() << " ms." << endl;
	cout << "It found " << lines.size() << " lines." << endl; // 걸리는 연산 시간을 출력
	cout << endl;

	cv::cvtColor(src, dst, COLOR_GRAY2BGR);

	for (size_t i = 0; i < lines.size(); i++) {
		Vec4i l = lines[i];
		line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 2, LINE_AA);
	}
}

void houghLinesGPU(const Mat& src, Mat& dst)
{
	GpuMat d_src, d_tmp;
	int64 t1, t2;
	double ms;

	t1 = getTickCount();
	d_tmp.upload(src);
	t2 = getTickCount();
	ms = (t2 - t1) * 1000 / getTickFrequency();
	cout << "GpuMat upload (1st try) took " << ms << " ms." << endl << endl;

	t1 = getTickCount();
	d_src.upload(src);
	t2 = getTickCount();
	ms = (t2 - t1) * 1000 / getTickFrequency();
	cout << "GpuMat upload (2nd try) took " << ms << " ms." << endl << endl;

	t1 = getTickCount();

	GpuMat d_edges;
	Ptr<CannyEdgeDetector> canny = createCannyEdgeDetector(100, 200, 3); // CannyEdgeDetector 클래스를 생성함
	canny->detect(d_src, d_edges); // detect 함수를 이용해서 edge를 검출

	GpuMat d_lines;
	Ptr<cuda::HoughSegmentDetector> hough = cuda::createHoughSegmentDetector(1.0f, (float)(CV_PI / 180.0f), 50, 5);
	// HoughSegmentDetector 클래스를 생성함
	hough->detect(d_edges, d_lines); // detect 함수를 이용해서 직선을 검출

	t2 = getTickCount();
	ms = (t2 - t1) * 1000 / getTickFrequency();
	cout << "Canny & Hough (GPU version) took " << ms << " ms." << endl;
	cout << "It found " << d_lines.cols << " lines." << endl;

	cv::cvtColor(src, dst, COLOR_GRAY2BGR);

	vector<Vec4i> lines_gpu;
	if (!d_lines.empty())
	{
		lines_gpu.resize(d_lines.cols);
		Mat h_lines(1, d_lines.cols, CV_32SC4, &lines_gpu[0]);
		d_lines.download(h_lines);
	}

	for (size_t i = 0; i < lines_gpu.size(); ++i)
	{
		Vec4i l = lines_gpu[i];
		line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 3, LINE_AA);
	}
}

  • CPU보다 GPU 연산시간이 월등히 빠름

image

  • OpenCV GPU Performance 테스트 예제
    • GPU를 사용하면 월등히 빨라지는 것을 확인할 수 있음

image

OpenCL 활용하기

  • 여러 개의 CPU, GPU, DSP 등의 프로세서로 이루어진 이종 플랫폼에서 동작하는 프로그램 코드 작성을 위한 개방형 범용 병렬 컴퓨팅 프레임워크

OpenCL 성능 분석

image

OpenCL을 사용하려면 어떤식으로 코드를 작성해야하는가?

  • OpenCV 2.x

image

  • OpenCV 3.x

image

OpenCL 예제코드

#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	VideoCapture cap("korea.mp4"); // 동영상 파일을 오픈

	if (!cap.isOpened()) {
		cerr << "Video open failed!" << endl;
		return -1;
	}

	// 간단한 정보를 출력
	int frame_width = cvRound(cap.get(CAP_PROP_FRAME_WIDTH));
	int frame_height = cvRound(cap.get(CAP_PROP_FRAME_HEIGHT));
	int frame_count = cvRound(cap.get(CAP_PROP_FRAME_COUNT));
	int fps = cvRound(cap.get(CAP_PROP_FPS));
	cout << "Frame width: " << frame_width << endl;
	cout << "Frame height: " << frame_height << endl;
	cout << "Frame count: " << frame_count << endl;
	cout << "FPS: " << fps << endl; 

	namedWindow("src");
	namedWindow("dst");

	UMat frame, gray, blr, edge; // Mat을 UMat으로만 변경하면 OpenCL을 사용할 수 있음
	while (true) {
		cap >> frame;
		if (frame.empty())
			break;

		TickMeter tm;
		tm.start();

		cvtColor(frame, gray, COLOR_BGR2GRAY); // Mat frame을 받아와서 그레이스케일로 변환
		bilateralFilter(gray, blr, -1, 10, 3); // 변환한 그레이스케일 양방향 필터 사용
		Canny(blr, edge, 50, 150); // 캐네 엣지 검출

		tm.stop();
		cout << "It took " << tm.getTimeMilli() << "ms." << endl; // 위 3가지 작업시간 출력

		imshow("src", frame);
		imshow("dst", edge);

		if (waitKey(10) == 27) // 약간의 딜레이를 주기 위해 10을 줌
			break;
	}
}

OpenCL T-API 사용시 주의사항

  • UMat을 사용한다고 항상 좋지는 않음
    • 주로 영상을 표현하는 Mat에 대해서만 UMat으로 변환 사용
  • Border 처리 연산시 BORDER_REPLICATE 옵션 권장
    • BORDER_REPLICATE : aaaaaaa abcdefgh hhhhhhh
  • Mat::getUMat() 또는 UMat::getMAt() 함수 사용시 주의사항
    • getUMat()함수를 통해 UMat 객체를 생성할 경우, 새로 생성한 UMat 객체가 완전히 소멸한 후 원본 Mat 객체를 사용
      • 아래 예제코드에서는 { } 괄호 안에서는 mat1은 건드리지 않고 지역 변수 umat1만 사용하는 것이 가급적으로 좋음

image