LoginSignup
1
4

More than 5 years have passed since last update.

cv::Matを遅く使う方法(img.at<uchar>(y,x) を使う全画素処理)

Last updated at Posted at 2015-08-29

OpenCVの自作ライブラリが、無駄に遅いことに気づいた。
全画素処理で無駄に15%程度遅い書き方をしていた。

結論:
img.at<uchar>(y,x)はランダムアクセス用に使う。
全画像処理を行う場合には、各行の先頭のポインタを求め、行の中では、一次元配列として
処理するのが可読性がよく、しかも速い。

ここで実験している範囲では上記の結論になりますが、
別の記事に書いているように、
画像処理で2重ループをなるべく書くな
と主張します。

pixelAccess.cpp
#include <opencv2/opencv.hpp>
/**
* @brief cetnter of gravity (img.at<uchar>(y,x) version)
* @param[in] img
* @param[out] meanX
* @param[out] meanY
* 画素の位置を求めるためには毎回、乗算が必要。
*/
void meanXY(const cv::Mat &img, double &meanX, double &meanY){
    double sumX = 0.0;
    double sumY = 0.0;
    double sum = 0.0;

    for (int y = 0; y < img.rows; y++){
        for (int x = 0; x < img.cols; x++){
            uchar pixel = img.at<uchar>(y, x);
            sumX += pixel * x;
            sumY += pixel * y;
            sum += pixel;
        }
    }
    meanX = sumX / sum;
    meanY = sumY / sum;

}

/**
* @brief cetnter of gravity (pointer version)
* @param[in] img
* @param[out] meanX
* @param[out] meanY
* 同じ行の中では、次の画素にアクセスするのに乗算は不要になる。
*/
void meanXY2(const cv::Mat &img, double &meanX, double &meanY){
    double sumX = 0.0;
    double sumY = 0.0;
    double sum = 0.0;

    for (int y = 0; y < img.rows; y++){
        const uchar *pLine = img.ptr<uchar>(y);
        for (int x = 0; x < img.cols; x++){
            uchar pixel = pLine[x];
            sumX += pixel * x;
            sumY += pixel * y;
            sum += pixel;
        }
    }
    meanX = sumX / sum;
    meanY = sumY / sum;

}

int main(int argc, char* argv[]){
    char name[] = "lena.jpg";
    cv::Mat img = cv::imread(name, 0);

    double f = 1000.0 / cv::getTickFrequency();
    int64 time = cv::getTickCount();

    double meanX;
    double meanY;
    for (int i = 0; i < 100; i++){
        meanXY(img, meanX, meanY);
    }

    int64 time2 = cv::getTickCount();

    double meanX2;
    double meanY2;
    for (int i = 0; i < 100; i++){
        meanXY2(img, meanX2, meanY2);
    }
    int64 time3 = cv::getTickCount();

    printf("meanX =%f\n", meanX);
    printf("meanY =%f\n", meanY);
    printf("meanX =%f\n", meanX2);
    printf("meanY =%f\n", meanY2);


    double percent = 100.0*(time3 - time2) / (time2 - time);

    printf("%f ms \n", (time2 - time)*f);
    printf("%f ms (%f %%)\n", (time3 - time2)*f, percent);
}

実行結果 (win32 release mode)
meanX =266.523905
meanY =247.455709
meanX =266.523905
meanY =247.455709
74.742080 ms
63.385786 ms (84.806024 %)

付記:
 OpenCVのモジュールの中にある関数で、自分の関数を置き換えられないか、OpenCVのドキュメントを読んでみよう。
 OpenCVの関数の中でマルチコアの対策がされている関数を使えば、素直に高速化することができます。
 ポインタ演算をするよりは、OpenMPの利用で速くする方がいい。

1
4
1

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
1
4