この記事はOpenCV Advent Calendar 2015の14日目の記事です.
はじめに
OpenCV 3.0からレンジの要素すべてに指定された関数を適用するcv::Mat::forEachメソッドが追加されました.詳細は公式ドキュメントを参照ください.
また,この機能はkazuki-ma氏によって実装が行われたものであり,Pull Requestのやり取りはhttps://github.com/Itseez/opencv/pull/2117にて読むことができます.
普段C++(C++11以降)を使っている方だとstd::for_eachのMat版みたいなものと考えるとイメージしやすいかもしれません.
cv::Mat::forEachメソッドの内部処理
ということで,modules/core/include/opencv2/core/utility.hpp
にあるMat::forEach_implの中身を追ってみましょう.以降,Mat::forEach_implの内部処理で重要なところをピックアップして紹介します.
途中で出てくるcv::ParallelLoopBodyや**cv::parallel_for_**は@fukushima1981さんの記事や筆者のwikiを参考にして下さい.
1. ライン数を計算する
const int LINES = static_cast<int>(this->total() / this->size[this->dims - 1]);
2. ParallelLoopBodyクラスを継承したピクセル操作関数を用意する
ParallelLoopBodyクラスを継承したピクセル操作関数を実装しています.
class PixelOperationWrapper :public ParallelLoopBody
{
public:
PixelOperationWrapper(Mat_<_Tp>* const frame, const Functor& _operation)
: mat(frame), op(_operation) {};
virtual ~PixelOperationWrapper(){};
// ! Overloaded virtual operator
// convert range call to row call.
virtual void operator()(const Range &range) const {
const int DIMS = mat->dims;
const int COLS = mat->size[DIMS - 1];
3. parallel_for_を使って並列処理する
**parallel_for_**によってピクセル操作を並列処理します.そのため,マルチコア環境であればパフォーマンス向上が期待できます.
parallel_for_(cv::Range(0, LINES), PixelOperationWrapper(reinterpret_cast<Mat_<_Tp>*>(this), operation));
サンプルコード
以下にcv::Mat::forEachメソッドとatメソッド(比較用),イテレータ(比較用),ポインタ(比較用)でピクセルにアクセスするサンプルコードを示します.
#include <opencv2/core.hpp>
#include <iostream>
// 画像サイズの定数
const cv::Size szXGA = cv::Size(1024, 768);
const cv::Size sz720p = cv::Size(1280, 720);
const cv::Size sz1080p = cv::Size(1920, 1080);
// 計測用マクロ
#define MEASUREMENT(func_name, sz, loop) \
measurement(#func_name, func_name, sz, loop);
void measurement(const std::string func_name,
void func(cv::Mat &), cv::Size sz, int loop)
{
cv::Mat image = cv::Mat::zeros(sz, CV_8UC3);
double f = 1000.0f / cv::getTickFrequency();
int64 start = cv::getTickCount();
for (int i = 0; i < loop; i++)
{
func(image);
}
int64 end = cv::getTickCount();
std::cout << func_name << ": " << (end - start) * f << "[ms]" << std::endl;
}
// atメソッドによるピクセル操作
void atAccess(cv::Mat &src)
{
for (int y = 0; y < src.rows; y++)
{
for (int x = 0; x < src.cols; x++)
{
src.at<cv::Vec3b>(y, x)[0] = 0;
src.at<cv::Vec3b>(y, x)[1] = 0;
src.at<cv::Vec3b>(y, x)[2] = 255;
}
}
}
// イテレータによるピクセル操作
void iteratorAccess(cv::Mat &src)
{
cv::MatIterator_<cv::Vec3b> itr = src.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> itr_end = src.end<cv::Vec3b>();
for (int i = 0; itr != itr_end; itr++, i++)
{
cv::Vec3b bgr = (*itr);
(*itr)[0] = 0;
(*itr)[1] = 0;
(*itr)[2] = 255;
}
}
// ポインタによるピクセル操作
void pointerAccess(cv::Mat &src)
{
for (int y = 0; y < src.rows; y++)
{
cv::Vec3b *p = &src.at<cv::Vec3b>(y, 0);
for (int x = 0; x < src.cols; x++)
{
(*p)[0] = 0;
(*p)[1] = 0;
(*p)[2] = 255;
p++;
}
}
}
// cv::Mat::forEachメソッドによるピクセル操作
void forEachAccess(cv::Mat &src)
{
src.forEach<cv::Vec3b>([](cv::Vec3b &p, const int * position) -> void {
p[0] = 0;
p[1] = 0;
p[2] = 255;
});
}
int main(int argc, char *argv[])
{
int loop = 100;
std::cout << "[XGA]" << std::endl;
MEASUREMENT(atAccess, szXGA, loop);
MEASUREMENT(iteratorAccess, szXGA, loop);
MEASUREMENT(pointerAccess, szXGA, loop);
MEASUREMENT(forEachAccess, szXGA, loop);
std::cout << "\n[720p]" << std::endl;
MEASUREMENT(atAccess, sz720p, loop);
MEASUREMENT(iteratorAccess, sz720p, loop);
MEASUREMENT(pointerAccess, sz720p, loop);
MEASUREMENT(forEachAccess, sz720p, loop);
std::cout << "\n[1080p]" << std::endl;
MEASUREMENT(atAccess, sz1080p, loop);
MEASUREMENT(iteratorAccess, sz1080p, loop);
MEASUREMENT(pointerAccess, sz1080p, loop);
MEASUREMENT(forEachAccess, sz1080p, loop);
return 0;
}
パフォーマンス
前述のサンプルコードを実行させた結果は以下の通りです.
この結果からもわかるようにcv::Mat::forEachメソッドの方が高速に処理できていることがわかります.
[XGA]
atAccess: 192.748[ms]
iteratorAccess: 194.007[ms]
pointerAccess: 55.3249[ms]
forEachAccess: 12.6574[ms]
[720p]
atAccess: 225.619[ms]
iteratorAccess: 228.75[ms]
pointerAccess: 64.8113[ms]
forEachAccess: 12.4458[ms]
[1080p]
atAccess: 509.205[ms]
iteratorAccess: 517.512[ms]
pointerAccess: 145.582[ms]
forEachAccess: 25.1963[ms]
また,筆者の計測環境は以下の通りです.
CPU:Intel Core i7-3930K 3.20GHz
メモリ:32GB
おわりに
この記事ではcv::Mat::forEachメソッドの内部処理および使い方を紹介しました.
備考
筆者は以下の環境で動作確認しました.
- OpenCV 3.0.0
- Windows 8.1 Pro(64bit)
- Visual Studio 2013 Update5