OpenCV 2系でGPUで膨張処理をさせる方法で詰まったので書いておく。最後に検証に使った全コードがあるのでどうぞ。
CPUで膨張処理をさせるとき、さくっと書けば次のようになる。
cv::Mat src, dst; int width = 256; int height = 256; src = cv::Mat::eye(cv::Size(width, height), CV_8U); cv::dilate(src, dst, cv::Mat::ones(3, 3, CV_8U)); std::cout << cv::countNonZero(dst) << " cpu dilate w/o rect" << std::endl;
この結果は、 256+255*2+254*2=1274
となる。
たとえば、周辺1ピクセルを膨張処理させなかった場合は、
cv::Rect rect = cv::Rect(1, 1, width - 2, height - 2); cv::dilate(src(rect), dst(rect), cv::Mat::ones(3, 3, CV_8U));
この結果は、 256+255*2+254*2-5*2=1264
となる。
GPU版では、領域外チェックが甘いため、全ての領域を処理させたとき、領域外のメモリを含めて計算してしまうバグ(仕様)がある。
それを検証するために、 cv::gpu::dilate
と、 cv::gpu::getMaxFilter_GPU
と、cv::gpu::createBoxFilter_GPU
を比較してみた。
それぞれ、全領域で処理させた場合 w/o rect
と、 rect
で周辺1ピクセルを無視した場合 with rect
を行った。
結果は次のとおり。
処理方法 | countNonZeroの結果 |
---|---|
gpu dilate w/o rect | 1264 |
gpu dilate with rect | 1249 |
gpu max w/o rect | 1278 |
gpu max with rect | 1264 |
gpu box w/o rect | 1259 |
gpu box with rect | 1264 |
w/o rect
の期待値は1274なので、いずれも正しい答えを返していない。一方で、 with rect
は cv::gpu::getMaxFilter_GPU
と、cv::gpu::createBoxFilter_GPU
で正しい答えを返している。
この2つ以外は、おそらく環境依存で値が変わりうると考えられる。ここで、BoxFilterを使うときは、下記の検証用コードでもそそしているとおり、要素を Expansion の二乗より大きい値にしておく必要がある。さらに、最後に適切な値に閾値処理する必要がある。これらの手間を考えると、 cv::gpu::getMaxFilter_GPU
を with rect
で使うのが最もシンプルな方法であろう。
なかなか難解なライブラリである。
さて、検証用全コード
#include <opencv2/opencv.hpp> #include <opencv2/gpu/gpu.hpp> using namespace cv; int main() { cv::gpu::setDevice(0); int gpudeviceid = cv::gpu::getDevice(); cv::gpu::DeviceInfo dev(gpudeviceid); string gpu_name = dev.name(); cv::gpu::Stream stream; int Expansion = 3; Ptr<cv::gpu::BaseFilter_GPU> fb; fb = cv::gpu::getMaxFilter_GPU(CV_8UC1, CV_8UC1, Size(Expansion, Expansion)); cv::Ptr<gpu::FilterEngine_GPU> filter; filter = gpu::createBoxFilter_GPU(CV_8U, CV_8U, cv::Size(Expansion, Expansion)); cv::gpu::GpuMat gsrc, gdst; cv::Mat src, dst; int width = 256; int height = 256; src = cv::Mat::eye(cv::Size(width, height), CV_8U) * 9; gsrc.upload(src); cv::Rect rect = cv::Rect(1, 1, width - 2, height - 2); { cv::dilate(src, dst, cv::Mat::ones(3, 3, CV_8U)); std::cout << cv::countNonZero(dst) << " cpu dilate w/o rect" << std::endl; } { dst = cv::Scalar(0); cv::dilate(src(rect), dst(rect), cv::Mat::ones(3, 3, CV_8U)); std::cout << cv::countNonZero(dst) << " cpu dilate with rect" << std::endl; } src = cv::Scalar(0); { gdst.upload(src); cv::gpu::dilate(gsrc, gdst, cv::Mat::ones(3, 3, CV_8U)); std::cout << cv::countNonZero(dst) << " gpu dilate w/o rect" << std::endl; } { gdst.upload(src); cv::gpu::dilate(gsrc(rect), gdst(rect), cv::Mat::ones(3, 3, CV_8U)); std::cout << cv::gpu::countNonZero(gdst) << " gpu dilate with rect" << std::endl; } { gdst.upload(src); (*fb)(gsrc, gdst); std::cout << cv::gpu::countNonZero(gdst) << " gpu max w/o rect" << std::endl; } { gdst.upload(src); (*fb)(gsrc(rect), gdst(rect)); //おすすめの方法 std::cout << cv::gpu::countNonZero(gdst) << " gpu max with rect" << std::endl; } { gdst.upload(src); filter->apply(gsrc, gdst); std::cout << cv::gpu::countNonZero(gdst) << " gpu box w/o rect" << std::endl; } { gdst.upload(src); filter->apply(gsrc, gdst, rect); std::cout << cv::gpu::countNonZero(gdst) << " gpu box with rect" << std::endl; } fb.release(); // no error stream.waitForCompletion(); std::cout << "Free memory rate = " << dev.freeMemory()*100.0 / dev.totalMemory() << " [%] at Device = " << gpudeviceid << " at first" << std::endl; return 0; }