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の利用で速くする方がいい。