AMD GPU OpenCV処理時間計測
AMD Ryzen 7 255のGPU「Radeon 780M Graphics」でOpenCV処理がどの程度高速化出来るものなのかを計測しました。#自分の忘備録8
OpenCVはOpenCL(Open Computing Language)をサポートしていて、AMD GPUではこれを利用した処理が可能です。最近のOpenCVは自分でソースから構築しなくてもいろいろな機能がすぐに使えるので大変素晴らしいです。cv::Matをcv::UMat(Unified Matrix)に置き換えることで、OpenCVがOpenCLを呼び出してAMD GPUを使用してくれます。
2026/04/06(※1)追記
何となく振るわない処理時間の数値が何でだかわかりました。Windows版のcmakeのデフォルトはDebugビルドなのですね。cmakeをあまり使っていないのがバレバレですが
WSL(Ubuntu)の同様の計測値がCPUなのに、あれ全然違う?ということで気が付きました。exeのディレクトリもDebugだったし計測数値もms単位ということで気が付ければ良かったですが、ロートルさで勘が鈍くなったというのが否めません![]()
それで改めてDebugビルドとReleaseビルドで再計測(Debugビルドも再計測しましたが傾向は以前と同じ)。
OpenCV処理
以下のOpenCV処理の時間計測を行います。
- 画像リサイズ(image_resize)
- スケーリング/シフト(scaleabsolute)
- ガンマ補正(gamma_correct)
- ヒストグラム均一化(histgram_equalize)
- 色空間変換(color_space)
- ガウシアンぼかし(gaussian_blur)
- シャープニング(filter_sharping)
OpenCV処理時間計測結果
OpenCV処理の時間計測にはsteady_clock(QueryPerformanceCounterをラップしている)を使用します。なので時間はかなり正確に計測出来ます。
プログラムはキャッシュやコンテキストスイッチの影響で1回目と連続した2回目以降の実行時間は結構異なるはずです。それを分けて計測します。
計測に使用した拙いC++ソースは終わりの test-opencv2.cpp です。USBカメラを接続して画像を取り込んで処理します(何となくそれらしい感じにしています)。
プログラムでは1回の処理毎の処理時間を計測して、コンテキストスイッチ後の1回目や連続した2回目以降の傾向を確かめながらExcelなどで平均化します。
下表の内容説明
- funcはOpenCV処理(具体的なOpenCV関数はプログラム内の同じ名前の関数を参照)
- 処理画像の大きさはUSBカメラの画素数(640×480)
- 数値は1回の処理時間で、単位は全て[us]
- cv::Matを使用したときはcpu、cv::UMatを使用したときはgpuと表現
- (1)は1回目、ave(2~10)は2~10回目の平均(2回目以降もバラツキがあるものもありましたが、単純に平均しました)
Debugビルドの計測結果(※1)
| func | cpu(1) | cpu ave(2~10) | gpu(1) | gpu ave(2~10) |
|---|---|---|---|---|
| image_resize | 2794 | 1985 | 245 | 18 |
| scaleabsolute | 7147 | 6076 | 263 | 24 |
| gamma_correct | 862 | 428 | 717 | 391 |
| histgram_equalize | 913 | 796 | 652 | 205 |
| color_space | 2102 | 1510 | 283 | 17 |
| gaussian_blur | 5509 | 5251 | 1043 | 931 |
| filter_sharping | 12639 | 11692 | 408 | 101 |
Releaseビルドの計測結果(※1)
| func | cpu(1) | cpu ave(2~10) | gpu(1) | gpu ave(2~10) |
|---|---|---|---|---|
| image_resize | 358 | 74 | 310 | 5 |
| scaleabsolute | 242 | 110 | 301 | 6 |
| gamma_correct | 584 | 208 | 735 | 316 |
| histgram_equalize | 472 | 187 | 598 | 37 |
| color_space | 568 | 148 | 349 | 5 |
| gaussian_blur | 497 | 130 | 949 | 891 |
| filter_sharping | 1061 | 360 | 352 | 22 |
- 処理にもよるがGPUはCPUよりかなり高速化される
- GPUは連続処理でキャッシュに入るとさらなる高速化が期待できる、本当の威力を発揮する感じでしょうか
- ReleaseビルドのCPUは安定して速くなっています。GPUの数値が大きいのは不得意なのがハッキリしているのか~OpenCLでは難しいのか??(※1)
計測プログラム構築例
OpenCVリリースサイトからWindows版の最新版OpenCV - 4.12.0(opencv-4.12.0-windows.exe)をダウンロードして使用します。
opencv-4.12.0-windows.exeを実行すると解凍が始まるので、適当なディレクトリ(今回はC:\)に解凍します。そうするとディレクトリ構成は以下のような感じ(必要なところだけ)。
C:\opencv\build\include
C:\opencv\build\x64\vc16\bin
C:\opencv\build\x64\vc16\lib
コンパイラはVisual Studio 2026を使用します。
C++ソースから構築するためにCMakeLists.txtファイルを作成するのですが、どうもOpenCV - 4.12.0はVisual Studio 2119(vc16)で構築されているようで、cmakeでOpenCVを見つけられるようなOpenCV_DIRを設定します(cmakeの詳細はわからないので適当に最低限)。
(CMakeLists.txt例)
# SPECIFY THE MINIMUM VERSION OF CMAKE REQUIRED
cmake_minimum_required(VERSION 3.31)
# SPECIFY YOUR PROJECT NAME
project(test-opencv2)
# C++20を設定
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# MAKE SURE OPENCV IS INSTALLED CORRECTLY
set(OpenCV_DIR "C:/opencv/build/x64/vc16/lib")
find_package(OpenCV REQUIRED)
# COMPILE CPP FILES USING THIS LINE
add_executable(test-opencv2 test-opencv2.cpp)
# TARGET INCLUDE AND LIBRARY
target_link_libraries(test-opencv2 PRIVATE ${OpenCV_LIBS})
(構築例)
cd test-opencv2 ← test-opencv2.cppとCMakeLists.txtを置いたディレクトリ
cmake -S . -B build
cmake --build build ← これはDebugビルドと同じだった
cmake --build build --config Debug ← Debugビルド(※1)
cmake --build build --config Release ← Releaseビルド(※1)
(実行例)
path C:\opencv\build\x64\vc16\bin;%path% ← OpenCVライブラリのパス追加
set OPENCV_LOG_LEVEL=ERROR ← OpenCVがいろいろログを出力するので、ERRORレベル以上を出力
build\Debug\test-opencv2.exe 0 0 0 ← 0=CamNo、0=image_resize、0=cv::Matで実行 Debugビルド(※1)
build\Release\test-opencv2.exe 0 0 0 ← 0=CamNo、0=image_resize、0=cv::Matで実行 Releaseビルド(※1)
参考にしたサイト
使用したプログラムソース test-opencv2.cpp
#include <opencv2/opencv.hpp>
#include <conio.h>
using namespace std::chrono; //steady_clock
#define countof(a) (sizeof(a)/sizeof(a[0]))
//image resize
//fx: 0.5
//fy: 0.5
int image_resize(cv::InputArray frame, cv::OutputArray frame2, double fx = 0.5, double fy = 0.5)
{
auto tm1 = steady_clock::now();
cv::resize(frame, frame2, cv::Size(), fx, fy);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//scale absolute
//alpha: 1.5; //scale
//beta: 50; //offset
int scale_absolute(cv::InputArray frame, cv::OutputArray frame2, double alpha = 1.5, double beta = 50)
{
auto tm1 = steady_clock::now();
cv::convertScaleAbs(frame, frame2, alpha, beta);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//gamma correct
//gamma: 2.0
int gamma_correct(cv::InputArray frame, cv::OutputArray frame2, double gamma = 2.0)
{
auto tm1 = steady_clock::now();
cv::Mat lookup_table(1, 256, CV_8U);
for (int i = 0; i < 256; ++i) {
lookup_table.at<uchar>(i) = cv::saturate_cast<uchar>(pow(i / 255.0, gamma) * 255.0);
}
if (frame.kind() == cv::_InputArray::MAT) {
cv::LUT(frame, lookup_table, frame2);
} else {
cv::UMat utable;
lookup_table.copyTo(utable);
cv::LUT(frame, utable, frame2);
}
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//gray scale
int gray_scale(cv::InputArray frame, cv::OutputArray frame2)
{
auto tm1 = steady_clock::now();
cv::cvtColor(frame, frame2, cv::COLOR_RGB2GRAY);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//histgram equalize
int histgram_equalize(cv::InputArray frame, cv::OutputArray frame2)
{
auto tm1 = steady_clock::now();
cv::equalizeHist(frame, frame2);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//color space conversion
int color_space(cv::InputArray frame, cv::OutputArray frame2)
{
auto tm1 = steady_clock::now();
cv::cvtColor(frame, frame2, cv::COLOR_BGR2HSV);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//gaussian blur
//ksize: 7
int gaussian_blur(cv::InputArray frame, cv::OutputArray frame2, int ksize = 7)
{
auto tm1 = steady_clock::now();
cv::GaussianBlur(frame, frame2, cv::Size(ksize, ksize), 0);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//filter sharping
int filter_sharping(cv::InputArray frame, cv::OutputArray frame2)
{
auto tm1 = steady_clock::now();
cv::Mat filter = (cv::Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cv::filter2D(frame, frame2, frame.depth(), filter);
auto tm2 = steady_clock::now();
int dtm = (int)duration_cast<microseconds>(tm2 - tm1).count();
return dtm;
}
//function name
// 0 1 2 3 4 5 6
const char *funcstr[] = {"image_resize", "scaleabsolute", "gamma_correct", "histgram_equalize", "color_space", "gaussian_blur", "filter_sharping"};
//main
int main(int argc, char **argv)
{
int camno = 0, func = 0, umat = 0, count = 0;
if (argc > 1) camno = atoi(argv[1]); //0~:camera no,-1:rtsp camera
if (argc > 2) func = atoi(argv[2]); //0~6:function no
if (argc > 3) umat = atoi(argv[3]); //0:mat,1:umat
if (func >= countof(funcstr)) return 1;
fprintf(stderr, "[funcstr=%s]\n", funcstr[func]);
cv::VideoCapture cap;
if (camno >= 0) {
cap.open(camno, cv::CAP_DSHOW);
} else {
const std::string rtsp_url = "rtsp://192.168.11.9/live";
cap.open(rtsp_url.c_str());
}
if (!cap.isOpened()) {
std::cerr << "Camera open error !!" << std::endl;
return 1;
}
cap.set(cv::CAP_PROP_BUFFERSIZE, 1);
int camw = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int camh = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
printf("#camno=%d w=%d h=%d func=%d umat=%d (funcstr=%s)\n", camno, camw, camh, func, umat, funcstr[func]);
printf("cur,num,ave,max,min\n");
try {
int count = 10, memnum = 20 * count;
int *mem = new int[memnum], *ptr = mem;
int num = 0, sum = 0, max = 0, min = INT_MAX;
cv::Mat frame, frame1, frame2;
cv::UMat uframe, uframe1, uframe2;
auto tmcap1 = steady_clock::now();
while (true) {
if (!cap.read(frame)) throw std::exception("#Capture error ??");
if (cv::waitKey(1) >= 0 || (_kbhit() && _getch())) break;
frame.copyTo(uframe); // copy UMat
auto tmcap2 = steady_clock::now();
if (duration_cast<microseconds>(tmcap2 - tmcap1).count() >= 200000) { //200ms毎に実行(コンテキストスイッチが入る)
tmcap1 = tmcap2;
//resize
//double size = 0.5;
//cv::resize(frame, frame, cv::Size(), size, size);
int dtm;
if (!umat) {
for (int i = 0; i < count; i++) { //Mat使用、count回連続実行
switch (func) {
case 0: //image resize
dtm = image_resize(frame, frame2);
break;
case 1: //scaleabsolute
dtm = scale_absolute(frame, frame2);
break;
case 2: //gamma correct
dtm = gamma_correct(frame, frame2);
break;
case 3: //histgram equalize
gray_scale(frame, frame1);
dtm = histgram_equalize(frame1, frame2);
break;
case 4: //color space conversion
dtm = color_space(frame, frame2);
break;
case 5: //gaussian blur
dtm = gaussian_blur(frame, frame2);
break;
case 6: //filter sharping
dtm = filter_sharping(frame, frame2);
break;
}
sum += dtm;
if (dtm > max) max = dtm;
if (dtm < min) min = dtm;
if (num++ < memnum) *ptr++ = dtm;
}
} else {
for (int i = 0; i < count; i++) { //UMat使用、count回連続実行
switch (func) {
case 0: //image resize
dtm = image_resize(uframe, uframe2);
break;
case 1: //scaleabsolute
dtm = scale_absolute(uframe, uframe2);
break;
case 2: //gamma correct
dtm = gamma_correct(uframe, uframe2);
break;
case 3: //histgram equalize
gray_scale(uframe, uframe1);
dtm = histgram_equalize(uframe1, uframe2);
break;
case 4: //color space conversion
dtm = color_space(uframe, uframe2);
break;
case 5: //gaussian blur
dtm = gaussian_blur(uframe, uframe2);
break;
case 6: //filter sharping
dtm = filter_sharping(uframe, uframe2);
break;
}
sum += dtm;
if (dtm > max) max = dtm;
if (dtm < min) min = dtm;
if (num++ < memnum) *ptr++ = dtm;
}
}
if (!umat) {
cv::imshow("Cam", frame);
cv::imshow("Cam(2)", frame2);
} else {
cv::imshow("Cam", uframe);
cv::imshow("Cam(2)", uframe2);
}
printf("%d,%d,%d,%d,%d\n", dtm, num, sum / num, max, min);
if (num >= memnum) break;
}
}
printf("#%d,%d,%d,%s,%s", camno, camw, camh, funcstr[func], umat ? "umat" : "mat");
for (int i = 0; i < (num < memnum ? num : memnum); i++) {
printf(i ? "," : ", ");
printf("%d", mem[i]);
}
printf("\n");
delete mem;
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
}
cv::destroyAllWindows();
cap.release();
return 0;
}