[2023-05-03] 1. OpenCV와 GPU사용
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 연산시간이 월등히 빠름
- OpenCV GPU Performance 테스트 예제
- GPU를 사용하면 월등히 빨라지는 것을 확인할 수 있음
OpenCL 활용하기
- 여러 개의 CPU, GPU, DSP 등의 프로세서로 이루어진 이종 플랫폼에서 동작하는 프로그램 코드 작성을 위한 개방형 범용 병렬 컴퓨팅 프레임워크
OpenCL 성능 분석
OpenCL을 사용하려면 어떤식으로 코드를 작성해야하는가?
- OpenCV 2.x
- OpenCV 3.x
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만 사용하는 것이 가급적으로 좋음
- getUMat()함수를 통해 UMat 객체를 생성할 경우, 새로 생성한 UMat 객체가 완전히 소멸한 후 원본 Mat 객체를 사용