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();
stream.waitForCompletion();
std::cout << "Free memory rate = " << dev.freeMemory()*100.0 / dev.totalMemory() << " [%] at Device = " << gpudeviceid << " at first" << std::endl;
return 0;
}