LoginSignup
27
28

More than 5 years have passed since last update.

OpenCV 3.0 で各要素を引数に処理を行う簡単なやり方(並列化も)

Last updated at Posted at 2015-06-21

ディスクロージャー:以下で紹介する forEach の実装者です。(2015/07/09追記)

OpenCV で画素処理、具体的には cv::Mat を使って各画素ごとに処理をする際のメモです。

cv::Mat::forEach(関数)

OpenCV 3.0 で、画像やボクセルデータを扱う基本型である cv::Mat および cv::Mat_<type> に、与えられた関数をそれぞれの要素上で実行する forEach メソッドが追加されました。公式ドキュメント(英語)

forEach メソッドの利点は、次の通りです。

  • 関数内で、cv::Mat の各要素の値を変更することができる
  • 単なる関数を渡すだけのお手軽処理で、シングルコアマシンでも大抵のイテレーション処理と同等か、それより早く動作する。
  • マルチコアマシンであれば、自動的に並列化され、かつ低オーバーヘッドである。

使用例

次の結果で、グラデーション画像が生成されます。

#include <opencv2/opencv.hpp>

typedef cv::Point3_<uint8_t> Pixel;

int main(const int an, const char* const* const as)
{
    cv::Mat_<Pixel> square(256, 256);

    square.forEach([](Pixel &p, const int position[2]) -> void {
        // Pixel の BGR(xyz) のうち、GR を、画素の位置に応じた値で塗る。
        p.x = 0; // blue
        p.y = position[0]; // green
        p.z = position[1]; //red
    });

    cv::imshow("Gradation", square);
    cv::waitKey();
}

Gyazo

速度比較

以下に、比較用コードと、関数の処理が軽い時のベンチマーク結果を張っておきます。

#include <iostream>
#include <opencv2/core.hpp>

typedef cv::Point3_<uint8_t> Pixel;
typedef cv::Mat_<Pixel> MatT;
const cv::Size SIZE_FULL_HD(1920, 1020);

void ShowResult(const std::string name, const double time) {
    std::cout << cv::format("%40s | %20.10f", name.c_str(), time) << std::endl;
    return;
}

void bench(const std::string func_name, void func(MatT&), const int time) {
    MatT img(SIZE_FULL_HD);
    img *= 0;

    const uint64_t start = cv::getCPUTickCount();
    for (int count = 0; count < time; ++count) {
        func(img);
    }
    const uint64_t end = cv::getCPUTickCount();

    const double time_elapsed = (end - start) / cv::getTickFrequency() / time;

    ShowResult(func_name, time_elapsed);
}

inline void iterator_access(MatT& frame) {
    auto it = frame.begin();

    for (auto &p : frame) {
        p.x = 255;
    }
}

inline void pointer_access(MatT& frame) {
    auto p = reinterpret_cast<Pixel*>(frame.data);
    const auto e = p + frame.cols * frame.rows;
    while (p < e)
    {
        p->x = 255;
        ++p;
    }
}

inline void forEach_access(MatT& frame) {
    auto mat = cv::Mat_<Pixel>(frame);

    mat.forEach([&](Pixel &p, const void*) -> void {
        p.x = 255;
    });
}

#define BENCH(func_name) \
    bench(#func_name, func_name, times);

int main(const int an, const char* const* const as)
{
#ifdef _DEBUG
    const int times = 1;
#else
    const int times = 1000;
#endif
    BENCH(iterator_access);
    BENCH(pointer_access);
    BENCH(forEach_access);
}

結果

                         iterator_access |         0.0050911927
                          pointer_access |         0.0014229820
                          forEach_access |         0.0003748687

4物理コアマシンで、pinter_access と forEach_access の速度比を求めると、3.7959 倍の高速化が得られました。関数が軽量でも、おおよそスケールしています。(=並列処理のオーバーヘッドは割と小さいです)

関数内の処理が重くなると、そこで律速するので、イテレーターとポインタアクセスは漸近していき、それらと forEach の速度の比は、コア数に近づいていきます。(3.7959 → 4.0 に近づきます)

実施マシン情報

(Windowws 上、dxdiag 実行結果の一部です)

------------------
System Information
------------------
Time of this report: 6/21/2015, 19:28:54
       Machine name: NEO
   Operating System: Windows Server 2012 R2 Datacenter 64-bit (6.3, Build 9600) (9600.winblue_r9.150322-1500)
           Language: Japanese (Regional Setting: Japanese)
System Manufacturer: Gigabyte Technology Co., Ltd.
       System Model: EX58-UD3R
               BIOS: Award Modular BIOS v6.00PG
          Processor: Intel(R) Core(TM) i7 CPU         920  @ 2.67GHz (8 CPUs), ~2.7GHz
             Memory: 20480MB RAM
Available OS Memory: 20478MB RAM
          Page File: 7164MB used, 23041MB available
        Windows Dir: C:\Windows
    DirectX Version: DirectX 11
DX Setup Parameters: Not found
   User DPI Setting: Using System DPI
 System DPI Setting: 96 DPI (100 percent)
    DWM DPI Scaling: Disabled
     DxDiag Version: 6.03.9600.17415 64bit Unicode

注意点

forEach に渡す関数の中身は、自動的に並列実行されます。

そのため、関数内から、その外部の特定変数に書き込む場合には、同時にほかのスレッドが同じ値を変更しないことを確認する必要があります。

合計値を求める処理(アキュムレーション)の場合には、cv::Accumulator やその周辺の専用関数を使ったほうがいいかと。

以下は違反コードの例です。

int main(const int an, const char* const* const as)
{
    cv::Mat_<Pixel> square(256, 256);
    long x_total = 0;

    square.forEach([&](Pixel &p, const int position[2]) -> void {
        x_total += p.x;
        // FIXME: 違反コード.複数スレッドから x_total への同時書き込み.
    });
}
27
28
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
27
28