せっかくグラボがあるのに腐っていたので、有益なことに使ってみます。OpenCVのcv::gpu名前空間以下にあるGPUモジュールを使い、CUDAの力を確認します。OpenCV 2.4.8です。
準備
注意点は3点。
- OpenCVは自分でビルド。CMakeではWITH_CUDAを有効にします。
- ターゲットは VisualStudio 2010にしました。(手はあるようですが)普通にやると、現状では2012以降向けにするとビルドでこけるようです。
- ビルドは数時間単位でかかりました。寝る前、出かける前などに。
CUDA付きのOpenCVになると、cv::gpu::getCudaEnabledDeviceCountが1以上を返すようになります。GPUが使えるかはこれで判定します。
int cudaDevices = cv::gpu::getCudaEnabledDeviceCount(); if (cudaDevices > 0) { // GPUが使えるときの処理 }
デバイス情報
cv::gpu::DeviceInfo で取得できます。我が家のGPU情報を表示させます。
using namespace std; cv::gpu::DeviceInfo info(0); cout << "[CUDA Device 0]" << endl << "name: " << info.name() << endl << "majorVersion: " << info.majorVersion() << endl << "minorVersion: " << info.minorVersion() << endl << "multiProcessorCount: " << info.multiProcessorCount() << endl << "sharedMemPerBlock: " << info.sharedMemPerBlock() << endl << "freeMemory: " << info.freeMemory() << endl << "totalMemory: " << info.totalMemory() << endl << "isCompatible: " << info.isCompatible() << endl << "supports(FEATURE_SET_COMPUTE_10): " << info.supports(FEATURE_SET_COMPUTE_10) << endl << "supports(FEATURE_SET_COMPUTE_11): " << info.supports(FEATURE_SET_COMPUTE_11) << endl << "supports(FEATURE_SET_COMPUTE_12): " << info.supports(FEATURE_SET_COMPUTE_12) << endl << "supports(FEATURE_SET_COMPUTE_13): " << info.supports(FEATURE_SET_COMPUTE_13) << endl << "supports(FEATURE_SET_COMPUTE_20): " << info.supports(FEATURE_SET_COMPUTE_20) << endl << "supports(FEATURE_SET_COMPUTE_21): " << info.supports(FEATURE_SET_COMPUTE_21) << endl << "supports(FEATURE_SET_COMPUTE_30): " << info.supports(FEATURE_SET_COMPUTE_30) << endl << "supports(FEATURE_SET_COMPUTE_35): " << info.supports(FEATURE_SET_COMPUTE_35) << endl;
GTX 650 Tiは、1スロットで済むお手頃な中ではまずまずの性能で、満足しています。
処理時間計測の注意
(CPUでも多少ありますが、)GPUは初回の処理で1秒ほど準備(?)にかかったりします。適当なダミー処理をさせてから計測しましょう。
thresholdの性能差
以降、私が興味があるいくつかの関数について、通常のCPUでの処理と、GPU(CUDA)での処理の処理速度を比べていきます。なお、TBBなどのオプションは付けずにビルドしているので、いくらかCPUが過小評価かもしれません。
GPUは先ほど示した通り、CPUは Core i7-2600 3.40GHz です。
まずは二値化です。5000回実行する時間を計ります。ターゲットはいつものようにレナさん。
using namespace std; using namespace cv; using namespace cv::gpu; Mat src = imread("C:\\Lenna.png", cv::IMREAD_GRAYSCALE); GpuMat srcg(src); const int Count = 5000; // CPU TickMeter meter; meter.start(); for (int i = 0; i < Count; i++) { Mat binary; threshold(src, binary, 128, 255, cv::THRESH_BINARY); } meter.stop(); cout << "Threshold (CPU): " << meter.getTimeMilli() << "ms" << endl; // GPU meter.reset(); meter.start(); for (int i = 0; i < Count; i++) { GpuMat binaryg; threshold(srcg, binaryg, 128, 255, cv::THRESH_BINARY); } meter.stop(); cout << "Threshold (GPU): " << meter.getTimeMilli() << "ms" << endl;
方法 | 処理時間[ms] |
---|---|
CPU | 53 |
GPU | 492 |
かえって低速になりました。万能ではないということですね。
※はじめはTHRESH_OTSUで実験しようと思ったのですが、GPUでは大津の手法が使えないようでした。
matchTemplateの性能差
次はテンプレートマッチングです。
対象画像は、Wikipediaにある100年前のNew York Timesにしました。大きさ 5988x7046 の巨大な画像です。
http://en.wikipedia.org/wiki/File:New_York_Times_Frontpage_1914-07-29.png
テンプレートは、ロゴの N の字です。
using namespace std; using namespace cv; using namespace cv::gpu; Mat org = imread("C:\\New_York_Times_Frontpage_1914-07-29.png"); Mat src; cvtColor(org, src, cv::COLOR_BGR2GRAY); Mat tmpl = src(cv::Rect(2015, 125, 330, 360)); GpuMat srcg(src); GpuMat tmplg(tmpl); // CPU TickMeter meter; meter.start(); { Mat result; matchTemplate(src, tmpl, result, cv::TM_CCOEFF_NORMED); } meter.stop(); cout << "MatchTemplate (CPU): " << meter.getTimeMilli() << "ms" << endl; // GPU meter.reset(); meter.start(); { GpuMat resultg; matchTemplate(srcg, tmplg, resultg, cv::TM_CCOEFF_NORMED); } meter.stop(); cout << "MatchTemplate (GPU): " << meter.getTimeMilli() << "ms" << endl;
方法 | 処理時間[ms] |
---|---|
CPU | 4762 |
GPU | 279 |
15倍以上、感動の速さです。
matchTemplateの性能差 (小さい画像)
New York Timesのような巨大な画像ではなく、小さな画像を何度もする場合はどうなるのかも試してみました。
ターゲットはレナさんで、テンプレートは100x100にしています。マッチング後のminMaxLocも込みで、100回行っています。
using namespace std; using namespace cv; using namespace cv::gpu; Mat src = imread("C:\\Lenna.png", cv::IMREAD_GRAYSCALE); Mat tmpl = src(cv::Rect(200, 200, 100, 100)); const int Count = 100; // CPU TickMeter meter; meter.start(); for (int i = 0; i < Count; i++) { Mat result; matchTemplate(src, tmpl, result, cv::TM_CCOEFF_NORMED); double min, max; cv::Point minLoc, maxLoc; minMaxLoc(result, &min, &max, &minLoc, &maxLoc); } meter.stop(); cout << "MatchTemplate (CPU): " << meter.getTimeMilli() << "ms" << endl; // GPU meter.reset(); meter.start(); for (int i = 0; i < Count; i++) { GpuMat srcg(src); GpuMat tmplg(tmpl); GpuMat resultg; matchTemplate(srcg, tmplg, resultg, cv::TM_CCOEFF_NORMED); double min, max; cv::Point minLoc, maxLoc; minMaxLoc(resultg, &min, &max, &minLoc, &maxLoc); } meter.stop(); cout << "MatchTemplate (GPU): " << meter.getTimeMilli() << "ms" << endl;
方法 | 処理時間[ms] |
---|---|
CPU | 1308 |
GPU | 910 |
差は小さくなりますが、それでもまだ高速です。
今回テストしたいシーンを想定して、わざと入力のGpuMat変数定義をforの中に入れています。これを外に出しても、時間はほとんど変わりませんでした。GpuMatへのuploadは無視できるほど高速なのか、はたまたコンパイラで最適化がされてしまったのか。
fastNlMeansDenoisingの性能差
以前の記事で紹介した、Non-local means filterの性能差を見てみます。CPUでは負荷の大きい重い処理でしたが、これがどう変わるでしょうか。
NLMeansのパラメータが妥当なのか自信が無いですが、簡単に試しておきます。
ターゲットは以前の記事同様にレナさんにノイズを付加したものです。ノイズ付加の関数は省略します。
GPU版NLMeansのリファレンスはこちら。GPU版APIでは search_window, block_size の指定順が逆なのがややこしいです。3種類の関数があり、いずれも試しました。
http://docs.opencv.org/modules/gpu/doc/image_processing.html?highlight=simplemethod#void%20gpu::FastNonLocalMeansDenoising::simpleMethod%28const%20GpuMat&%20src,%20GpuMat&%20dst,%20float%20h,%20int%20search_window%20,%20int%20block_size%20,%20Stream&%20s%29
using namespace std; using namespace cv; using namespace cv::gpu; Mat src = imread("C:\\Lenna.png", cv::IMREAD_COLOR); const int NoiseSigma = 15; cv::Mat noise; addNoise(src, noise, NoiseSigma); Mat result, resultPure, resultSimple, resultLab; GpuMat resultg1, resultg2, resultg3; // CPU TickMeter meter; meter.start(); { cv::fastNlMeansDenoising(noise, result, NoiseSigma, 3, 7); } meter.stop(); cout << "NLMeans (CPU): " << meter.getTimeMilli() << "ms"; cout << " PSNR = " << PSNR(src, result) << endl; // GPU - pure meter.reset(); meter.start(); { GpuMat noiseg(noise); nonLocalMeans(noiseg, resultg1, NoiseSigma, 7, 3); } meter.stop(); resultg1.download(resultPure); cout << "NLMeans (GPU - pure): " << meter.getTimeMilli() << "ms"; cout << " PSNR = " << PSNR(src, resultPure) << endl; // GPU - simpleMethod meter.reset(); meter.start(); { GpuMat noiseg(noise); FastNonLocalMeansDenoising nlMeans; nlMeans.simpleMethod(noiseg, resultg2, NoiseSigma, 7, 3); } meter.stop(); resultg2.download(resultSimple); cout << "NLMeans (GPU - simpleMethod): " << meter.getTimeMilli() << "ms"; cout << " PSNR = " << PSNR(src, resultSimple) << endl; // GPU - labMethod meter.reset(); meter.start(); { GpuMat noiseg(noise); FastNonLocalMeansDenoising nlMeans; nlMeans.labMethod(noiseg, resultg3, NoiseSigma, NoiseSigma, 7, 3); } meter.stop(); resultg3.download(resultLab); cout << "NLMeans (GPU - labMethod): " << meter.getTimeMilli() << "ms"; cout << " PSNR = " << PSNR(src, resultLab) << endl;
処理時間はやはり速いです。GPUならリアルタイム動画処理でも使えそうな高速さです。PSNRもほとんど差がありません。
方法 | 処理時間[ms] | PSNR[db] |
---|---|---|
CPU | 175 | 31.88 |
GPU(pure) | 32 | 31.70 |
GPU(simpleMethod) | 47 | 31.89 |
GPU(labMethod) | 73 | 32.44 |
以下はそれぞれの出力画像です。特に一番下から2つは近似して高速計算する手法ということで、結果が多少悪くなると思われましたが、見た限りではほとんどわかりません。
近似手法(simpleMethod)よりも普通の実装(pure)が速いのが納得いきませんが...
元画像(ノイズ付加)
cv::fastNlMeansDenoising
cv::gpu::nonLocalMeans
cv::gpu::FastNonLocalMeansDenoising::simpleMethod
cv::gpu::FastNonLocalMeansDenoising::labMethod