cv::Mat::forEachを使った高速なピクセル操作

この記事は[OpenCV Advent Calendar 2015](http://qiita.com/advent-calendar/2015/opencv)の14日目の記事です．

# はじめに
OpenCV 3.0からレンジの要素すべてに指定された関数を適用する**cv::Mat::forEach**メソッドが追加されました．詳細は[公式ドキュメント](http://docs.opencv.org/master/d3/d63/classcv_1_1Mat.html#a952ef1a85d70a510240cb645a90efc0d)を参照ください．

また，この機能は[kazuki-ma](https://github.com/kazuki-ma)氏によって実装が行われたものであり，Pull Requestのやり取りは<https://github.com/Itseez/opencv/pull/2117>にて読むことができます．

普段C++(C++11以降)を使っている方だと[std::for_each](http://en.cppreference.com/w/cpp/algorithm/for_each)のMat版みたいなものと考えるとイメージしやすいかもしれません．

# cv::Mat::forEachメソッドの内部処理
ということで，<code>modules/core/include/opencv2/core/utility.hpp</code>にある**Mat::forEach_impl**の中身を追ってみましょう．以降，**Mat::forEach_impl**の内部処理で重要なところをピックアップして紹介します．

途中で出てくる**cv::ParallelLoopBody**や**cv::parallel_for_**は[@fukushima1981](https://twitter.com/fukushima1981)さんの[記事](http://qiita.com/fukushima1981/items/02d6c034ca0375496b65)や筆者の[wiki](https://github.com/atinfinity/lab/wiki/cv%3A%3Aparallel_for_%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)を参考にして下さい．

## 1. ライン数を計算する

```cpp:
const int LINES = static_cast<int>(this->total() / this->size[this->dims - 1]);
```

## 2. ParallelLoopBodyクラスを継承したピクセル操作関数を用意する
**ParallelLoopBody**クラスを継承したピクセル操作関数を実装しています．

```cpp:
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_**によってピクセル操作を並列処理します．そのため，マルチコア環境であればパフォーマンス向上が期待できます．

```cpp:
parallel_for_(cv::Range(0, LINES), PixelOperationWrapper(reinterpret_cast<Mat_<_Tp>*>(this), operation));
```

# サンプルコード
以下に**cv::Mat::forEach**メソッドと**at**メソッド（比較用），イテレータ（比較用），ポインタ（比較用）でピクセルにアクセスするサンプルコードを示します．

```cpp:
#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**メソッドの方が高速に処理できていることがわかります．

![graph.png](https://qiita-image-store.s3.amazonaws.com/0/31158/ac9236a5-f561-7d32-45ae-efa952848497.png)

<pre>
[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]
</pre>

また，筆者の計測環境は以下の通りです．
CPU：Intel Core i7-3930K 3.20GHz
メモリ：32GB

# おわりに
この記事では**cv::Mat::forEach**メソッドの内部処理および使い方を紹介しました．

# 備考
筆者は以下の環境で動作確認しました．

* [OpenCV 3.0.0](https://github.com/Itseez/opencv/releases/tag/3.0.0)
* Windows 8.1 Pro（64bit）
* Visual Studio 2013 Update5