OpenCV OpenMP - eiichiromomma/CVMLAB GitHub Wiki

(OpenCV) OpenMPによるマルチスレッド並列プログラミング

※試行錯誤中なので参考程度

導入についてはVisualC OpenMPを参照。

勉強を兼ねたお遊び

forループを並列化

ルール

#pragma omp parallel for [schedule(スケジューリング法)]

をfor文の直前に入れる。

サンプルソース

  • 100x100の画像をcvSet2Dで塗り潰すのをx, yについて並列化
  • dynamic, static, guidedの3種を順次y, xについて行なう
  • スレッド数はプロセッサ数*4
  #include <cv.h>
  #include <highgui.h>
  #include <omp.h>

  int main(void){
    IplImage *src = cvCreateImage(cvSize(100,100),IPL_DEPTH_8U,3);
    cvSetZero(src);
    int y,x;
    cvNamedWindow("src",1);
    
    int nProc = omp_get_num_procs();
    int nTh = nProc * 4;
    omp_set_num_threads(nTh);
    CvScalar *colors = new CvScalar[nTh];
    for (x = 0; x< nTh; x++){
      colors[x] = CV_RGB(255,255-x*255/nTh,x*255/nTh);
    }
    //dynamic で y のparallel
    //スレッドでxを共有するので private(x)が必要
  #pragma omp parallel for schedule(dynamic) private(x)
    for (y = 0; y< src->height; y++){
      for (x = 0; x< src->width; x++){
        cvSet2D(src,y,x,colors[omp_get_thread_num()]);
        cvShowImage("src",src);
        cvWaitKey(10);
      }
    }
    cvShowImage("src",src);
    cvSaveImage("OpenMP_d_y.png",src);
    cvWaitKey(0);
    cvSetZero(src);
    //static で y のparallel
    //スレッドでxを共有するので private(x)が必要
  #pragma omp parallel for schedule(static) private(x)
    for (y = 0; y< src->height; y++){
      for (x = 0; x< src->width; x++){
        cvSet2D(src,y,x,colors[omp_get_thread_num()]);
        cvShowImage("src",src);
        cvWaitKey(10);
      }
    }
    cvShowImage("src",src);
    cvSaveImage("OpenMP_s_y.png",src);
    cvWaitKey(0);
    cvSetZero(src);
    //guided で y のparallel
    //スレッドでxを共有するので private(x)が必要
  #pragma omp parallel for schedule(guided) private(x)
    for (y = 0; y< src->height; y++){
      for (x = 0; x< src->width; x++){
        cvSet2D(src,y,x,colors[omp_get_thread_num()]);
        cvShowImage("src",src);
        cvWaitKey(10);
      }
    }
    cvShowImage("src",src);
    cvSaveImage("OpenMP_g_y.png",src);
    cvWaitKey(0);
    cvSetZero(src);
    //dynamic で x のparallel
    for (y = 0; y< src->height; y++){
  #pragma omp parallel for schedule(dynamic)
      for (x = 0; x< src->width; x++){
        cvSet2D(src,y,x,colors[omp_get_thread_num()]);
        cvShowImage("src",src);
        cvWaitKey(10);
      }
    }
    cvShowImage("src",src);
    cvSaveImage("OpenMP_d_x.png",src);
    cvWaitKey(0);
    cvSetZero(src);
    //dynamic で x のparallel
    for (y = 0; y< src->height; y++){
  #pragma omp parallel for schedule(static)
      for (x = 0; x< src->width; x++){
        cvSet2D(src,y,x,colors[omp_get_thread_num()]);
        cvShowImage("src",src);
        cvWaitKey(10);
      }
    }
    cvShowImage("src",src);
    cvSaveImage("OpenMP_s_x.png",src);
    cvWaitKey(0);
    cvSetZero(src);
    //guided で x のparallel
    for (y = 0; y< src->height; y++){
  #pragma omp parallel for schedule(guided)
      for (x = 0; x< src->width; x++){
        cvSet2D(src,y,x,colors[omp_get_thread_num()]);
        cvShowImage("src",src);
        cvWaitKey(10);
      }
    }
    cvShowImage("src",src);
    cvSaveImage("OpenMP_g_x.png",src);
    cvWaitKey(0);
    return 0;
  }

処理の結果

yの並列化

dynamic

最初は規則的だが処理が終わったスレッドに順次割り当てるので下に行く程バラ付く。

static

等間隔で割り当てるので最後まで綺麗に分かれる。 当たり前だが一つでもモタ付くとそのスレッドが終わるまで待ちになるので、演算量が均等な処理以外は使うべきでは無い?

guided

対数的(?)に割り当てるので面白い。 文献によると効率は最も良いが、割り当ての計算コストがかかるとか。

xの並列化

dynamic

static

guided

OpenCV自体の実装

_makeからビルドしようとするとRelease OpenMPといった構成があるので、どの程度対応しているのかpragma ompでgrepしてみたところ、

  • apps/HaarTraining
  • cvDistTransform
  • cvHarr
  • Pyramid関係
  • auxのblobtracking

でしか使われていないらしい。

CVS版だと

  • cvMatchTemplate

も対応している。

つまり上記に該当する機能を使う場合は、OpenMPの構成を使った方がパフォーマンス向上の可能性がある。

以降の話は上記以外の処理をOpenMPに渡すサンプルなので、OpenCVに限った話ではない。

使い方

  1. Express Editionより上のVisualC++を使う または Windows SDK for Windows Server 2008を入れると使える
  2. #include <omp.h>を入れる
  3. 並列化したい所にOpenMPの宣言子を入れる
  4. プロジェクトの構成プロパティで[C/C++]-[言語]でOpenMPサポートを"はい/openmp"にする

参考資料

単純な例

細線化で白と黒のマッチング部分を並列化してみる。使用するデータが独立しているので細かいことは考えずに並列化できた。

宣言子は入れ子になっていて、parallelの中でsectionsを使った場合、sections内で宣言されたsectionを並列に処理する。

今のところこれで動いている。大きい画像を処理しようとすると10~20%パフォーマンスが向上した。

  #include <cv.h>
  #include <highgui.h>
  #include <omp.h>

  void myThinningInit(CvMat** kpw, CvMat** kpb)
  {
    for (int i=0; i<8; i++){
      *(kpw+i) = cvCreateMat(3, 3, CV_8UC1);
      *(kpb+i) = cvCreateMat(3, 3, CV_8UC1);
      cvSet(*(kpw+i), cvRealScalar(0), NULL);
      cvSet(*(kpb+i), cvRealScalar(0), NULL);
    }
    //kernel1
    cvSet2D(*(kpb+0), 0, 0, cvRealScalar(1));
    cvSet2D(*(kpb+0), 0, 1, cvRealScalar(1));
    cvSet2D(*(kpb+0), 1, 0, cvRealScalar(1));
    cvSet2D(*(kpw+0), 1, 1, cvRealScalar(1));
    cvSet2D(*(kpw+0), 1, 2, cvRealScalar(1));
    cvSet2D(*(kpw+0), 2, 1, cvRealScalar(1));
    //kernel2
    cvSet2D(*(kpb+1), 0, 0, cvRealScalar(1));
    cvSet2D(*(kpb+1), 0, 1, cvRealScalar(1));
    cvSet2D(*(kpb+1), 0, 2, cvRealScalar(1));
    cvSet2D(*(kpw+1), 1, 1, cvRealScalar(1));
    cvSet2D(*(kpw+1), 2, 0, cvRealScalar(1));
    cvSet2D(*(kpw+1), 2, 1, cvRealScalar(1));
    //kernel3
    cvSet2D(*(kpb+2), 0, 1, cvRealScalar(1));
    cvSet2D(*(kpb+2), 0, 2, cvRealScalar(1));
    cvSet2D(*(kpb+2), 1, 2, cvRealScalar(1));
    cvSet2D(*(kpw+2), 1, 0, cvRealScalar(1));
    cvSet2D(*(kpw+2), 1, 1, cvRealScalar(1));
    cvSet2D(*(kpw+2), 2, 1, cvRealScalar(1));
    //kernel4
    cvSet2D(*(kpb+3), 0, 2, cvRealScalar(1));
    cvSet2D(*(kpb+3), 1, 2, cvRealScalar(1));
    cvSet2D(*(kpb+3), 2, 2, cvRealScalar(1));
    cvSet2D(*(kpw+3), 0, 0, cvRealScalar(1));
    cvSet2D(*(kpw+3), 1, 0, cvRealScalar(1));
    cvSet2D(*(kpw+3), 1, 1, cvRealScalar(1));
    //kernel5
    cvSet2D(*(kpb+4), 1, 2, cvRealScalar(1));
    cvSet2D(*(kpb+4), 2, 2, cvRealScalar(1));
    cvSet2D(*(kpb+4), 2, 1, cvRealScalar(1));
    cvSet2D(*(kpw+4), 0, 1, cvRealScalar(1));
    cvSet2D(*(kpw+4), 1, 1, cvRealScalar(1));
    cvSet2D(*(kpw+4), 1, 0, cvRealScalar(1));
    //kernel6
    cvSet2D(*(kpb+5), 2, 0, cvRealScalar(1));
    cvSet2D(*(kpb+5), 2, 1, cvRealScalar(1));
    cvSet2D(*(kpb+5), 2, 2, cvRealScalar(1));
    cvSet2D(*(kpw+5), 0, 2, cvRealScalar(1));
    cvSet2D(*(kpw+5), 0, 1, cvRealScalar(1));
    cvSet2D(*(kpw+5), 1, 1, cvRealScalar(1));
    //kernel7
    cvSet2D(*(kpb+6), 1, 0, cvRealScalar(1));
    cvSet2D(*(kpb+6), 2, 0, cvRealScalar(1));
    cvSet2D(*(kpb+6), 2, 1, cvRealScalar(1));
    cvSet2D(*(kpw+6), 0, 1, cvRealScalar(1));
    cvSet2D(*(kpw+6), 1, 1, cvRealScalar(1));
    cvSet2D(*(kpw+6), 1, 2, cvRealScalar(1));
    //kernel8
    cvSet2D(*(kpb+7), 0, 0, cvRealScalar(1));
    cvSet2D(*(kpb+7), 1, 0, cvRealScalar(1));
    cvSet2D(*(kpb+7), 2, 0, cvRealScalar(1));
    cvSet2D(*(kpw+7), 1, 1, cvRealScalar(1));
    cvSet2D(*(kpw+7), 1, 2, cvRealScalar(1));
    cvSet2D(*(kpw+7), 2, 2, cvRealScalar(1));
  }

  void main(void)
  {
    CvMat** kpb = new CvMat *[8];
    CvMat** kpw = new CvMat *[8];
    myThinningInit(kpw, kpb);
    IplImage* src = cvLoadImage("thinning_testL.jpg",0);
    IplImage* dst = cvCloneImage(src);
    IplImage* src_w = cvCreateImage(cvGetSize(src), IPL_DEPTH_32F, 1);
    IplImage* src_b = cvCreateImage(cvGetSize(src), IPL_DEPTH_32F, 1);
    IplImage* src_f = cvCreateImage(cvGetSize(src), IPL_DEPTH_32F, 1);
    cvScale(src, src_f, 1/255.0, 0);
    cvThreshold(src_f,src_f,0.5,1.0,CV_THRESH_BINARY);
    cvThreshold(src_f,src_w,0.5,1.0,CV_THRESH_BINARY);
    cvThreshold(src_f,src_b,0.5,1.0,CV_THRESH_BINARY_INV);
    //使えるプロセッサ数をスレッド数に(今回はデュアルで足りる)
    int nProcs = omp_get_num_procs();
    omp_set_num_threads(nProcs);
    double sum=1;
    CvMat* w;
    CvMat* b;
    while(sum>0){
      sum=0;
      for (int i=0; i<8; i++){
        w= *(kpw+i);
        b= *(kpb+i);
  #pragma omp parallel
  {
  #pragma omp sections
    {
  #pragma omp section
      {
            cvFilter2D(src_w, src_w, w);
          cvThreshold(src_w,src_w,2.99,1,CV_THRESH_BINARY);
      }
  #pragma omp section
      {
            cvFilter2D(src_b, src_b, b);
          cvThreshold(src_b,src_b,2.99,1,CV_THRESH_BINARY);
      }
    }
  }
          cvAnd(src_w, src_b, src_w);
          sum += cvSum(src_w).val[0];
          cvXor(src_f, src_w, src_f);
          cvCopyImage(src_f, src_w);
          cvThreshold(src_f,src_b,0.5,1,CV_THRESH_BINARY_INV);
      }
    }
      
    cvConvertScaleAbs(src_f, dst, 255, 0);
    cvNamedWindow("dst",1);
    cvShowImage("dst",dst);
    cvWaitKey(0);
  }
⚠️ **GitHub.com Fallback** ⚠️