LoginSignup
54
56

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-13

この記事は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::ParallelLoopBodycv::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メソッドの方が高速に処理できていることがわかります.

graph.png


[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
54
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
56