画像の二値化

No comments 05 3月 2010 Under: opencv2.0-sample

C

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

int
main(int argc, char **argv)
{
  IplImage *src_img=0, *gray_img;
  IplImage *bin_img, *bininv_img, *trunc_img, *tozero_img, *tozeroinv_img;
  IplImage *adaptive_img;
  char *imagename;

  // (1)load a specified file and convert it into grayscale image.
  //    allocate destination images.
  imagename = argc > 1 ? argv[1] : "../image/trains.png";
  src_img = cvLoadImage(imagename, CV_LOAD_IMAGE_COLOR);
  if(src_img == 0)
    return -1;
  gray_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);
  cvCvtColor(src_img, gray_img, CV_BGR2GRAY);  
  bin_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);
  bininv_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);
  trunc_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);
  tozero_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);
  tozeroinv_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);
  adaptive_img = cvCreateImage(cvGetSize(src_img), IPL_DEPTH_8U, 1);

  // (2)apply a fixed-level threshold to each pixel
  cvThreshold(gray_img, bin_img, 0, 255, CV_THRESH_BINARY|CV_THRESH_OTSU);
  cvThreshold(gray_img, bininv_img, 0, 255, CV_THRESH_BINARY_INV|CV_THRESH_OTSU);
  cvThreshold(gray_img, trunc_img, 0, 255, CV_THRESH_TRUNC|CV_THRESH_OTSU);
  cvThreshold(gray_img, tozero_img, 0, 255, CV_THRESH_TOZERO|CV_THRESH_OTSU);
  cvThreshold(gray_img, tozeroinv_img, 0, 255, CV_THRESH_TOZERO_INV|CV_THRESH_OTSU);

  // (3)apply an adaptive threshold to a grayscale image
  cvAdaptiveThreshold(gray_img, adaptive_img, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 7, 8);
  
  // (4)show source and destination images 
  cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Binary", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Binary Inv", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Trunc", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("ToZero", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("ToZero Inv", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Adaptive", CV_WINDOW_AUTOSIZE);
  cvShowImage("Source", src_img);
  cvShowImage("Binary", bin_img);
  cvShowImage("Binary Inv", bininv_img);
  cvShowImage("Trunc", trunc_img);
  cvShowImage("ToZero", tozero_img);
  cvShowImage("ToZero Inv", tozeroinv_img);
  cvShowImage("Adaptive", adaptive_img);
  cvWaitKey (0);

  cvDestroyWindow("Source");
  cvDestroyWindow("Binary");
  cvDestroyWindow("Binary Inv");
  cvDestroyWindow("Trunc");
  cvDestroyWindow("ToZero");
  cvDestroyWindow("ToZero Inv");
  cvDestroyWindow("Adaptive");
  cvReleaseImage(&src_img);
  cvReleaseImage(&gray_img);
  cvReleaseImage(&bin_img);
  cvReleaseImage(&bininv_img);
  cvReleaseImage(&trunc_img);
  cvReleaseImage(&tozero_img);
  cvReleaseImage(&tozeroinv_img);
  cvReleaseImage(&adaptive_img);

  return 0;
}

// (1)指定ファイルをカラー画像として読み込み,それをグレースケールに変換します.

指定されたファイルを3チャンネルのカラー画像として読み込み,関数 cvCvtColor を用いてグレースケールに変換します.また,さまざまな閾値処理後の画像を格納する領域を確保します.これらは,入力画像と同じサイズで,ビット深度8,チャンネル1の画像領域になります.

// (2)画像に対し,さまざまな固定閾値処理を行います.

関数 cvThreshold を用いて,入力画像から作成されたグレースケール画像に対して,以下のような固定閾値処理を行います.

  • CV_THRESH_BINARY : 閾値を超えるピクセルは maxVal に,それ以外のピクセルは 0 になります.
  • CV_THRESH_BINARY_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは maxVal になります.
  • CV_THRESH_TRUNC : 閾値を超えるピクセルは threshold に,それ以外のピクセルは変更されません.
  • CV_THRESH_TOZERO : 閾値を超えるピクセルは変更されず,それ以外のピクセルは 0 になります.
  • CV_THRESH_TOZERO_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは変更されません.

また,このサンプルでは,全ての手法に対して CV_THRESH_OTSU を指定することで,大津の手法を用いて自動的に閾値を決定します.もちろん,これを行わずに自分で閾値を決定することも可能です.

// (3)画像に対し,適応的な閾値処理を行います.

関数 cvAdaptiveThreshold を用いて,入力画像から作成されたグレースケール画像に対して,適応的な閾値処理を行います.閾値が適応的に決定されるので,対象ピクセルは,画像全体ではなく,その近傍領域に対して明暗を決定されます.
ここでは第4引数で CV_ADAPTIVE_THRESH_GAUSSIAN_C を指定しているので,blockSize(第6引数)x blockSize の近傍領域に対してガウシアンを重みとして総和をとり,そこから param1(第7引数)の値を引いた値を閾値として利用します.
このように求められた閾値を超えるピクセルは maxVal (第3引数)に,それ以外は 0 になります.

// (4)入力画像と処理結果画像を表示します.

入力画像と,処理されたそれぞれの二値化画像を表示します.また,何かキーが押されるとプログラムを終了します.

C++

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

using namespace cv;

int
main(int argc, char **argv)
{
  // (1)load a specified file and convert it into grayscale image
  const char *imagename = argc > 1 ? argv[1] : "../image/trains.png";
  Mat src_img = imread(imagename, 1);
  if(!src_img.data)
    return -1;
  Mat gray_img;
  cvtColor(src_img, gray_img, CV_BGR2GRAY);
  
  // (2)apply a fixed-level threshold to each pixel
  Mat bin_img, bininv_img, trunc_img, tozero_img, tozeroinv_img;
  threshold(gray_img, bin_img, 0, 255, THRESH_BINARY|THRESH_OTSU);
  threshold(gray_img, bininv_img, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);
  threshold(gray_img, trunc_img, 0, 255, THRESH_TRUNC|THRESH_OTSU);
  threshold(gray_img, tozero_img, 0, 255, THRESH_TOZERO|THRESH_OTSU);
  threshold(gray_img, tozeroinv_img, 0, 255, THRESH_TOZERO_INV|THRESH_OTSU);

  // (3)apply an adaptive threshold to a grayscale image
  Mat adaptive_img;
  adaptiveThreshold(gray_img, adaptive_img, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 7, 8);

  // (4)show source and destination images 
  namedWindow("Source", CV_WINDOW_AUTOSIZE);
  namedWindow("Binary", CV_WINDOW_AUTOSIZE);
  namedWindow("Binary Inv", CV_WINDOW_AUTOSIZE);
  namedWindow("Trunc", CV_WINDOW_AUTOSIZE);
  namedWindow("ToZero", CV_WINDOW_AUTOSIZE);
  namedWindow("ToZero Inv", CV_WINDOW_AUTOSIZE);
  namedWindow("Adaptive", CV_WINDOW_AUTOSIZE);
  imshow("Source", src_img);
  imshow("Binary", bin_img);
  imshow("Binary Inv", bininv_img);
  imshow("Trunc", trunc_img);
  imshow("ToZero", tozero_img);
  imshow("ToZero Inv", tozeroinv_img);
  imshow("Adaptive", adaptive_img);
  waitKey(0);

  return 0;
}

// (1)指定ファイルをカラー画像として読み込み,それをグレースケールに変換します.

指定されたファイルを3チャンネルのカラー画像として読み込み,関数 cvtColor を用いてグレースケールに変換します.

// (2)画像に対し,さまざまな固定閾値処理を行います.

関数 threshold を用いて,入力画像から作成されたグレースケール画像に対して,以下のような固定閾値処理を行います(C インタフェースとは,定数の名前が異なることに注意してください).

  • THRESH_BINARY : 閾値を超えるピクセルは maxVal に,それ以外のピクセルは 0 になります.
  • THRESH_BINARY_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは maxVal になります.
  • THRESH_TRUNC : 閾値を超えるピクセルは threshold に,それ以外のピクセルは変更されません.
  • THRESH_TOZERO : 閾値を超えるピクセルは変更されず,それ以外のピクセルは 0 になります.
  • THRESH_TOZERO_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは変更されません.

また,このサンプルでは,全ての手法に対して THRESH_OTSU を指定することで,大津の手法を用いて自動的に閾値を決定します.もちろん,これを行わずに自分で閾値を決定することも可能です.

// (3)画像に対し,適応的な閾値処理を行います.

関数 adaptiveThreshold を用いて,入力画像から作成されたグレースケール画像に対して,適応的な閾値処理を行います.閾値が適応的に決定されるので,対象ピクセルは,画像全体ではなく,その近傍領域に対して明暗を決定されます.
ここでは第4引数で ADAPTIVE_THRESH_GAUSSIAN_C を指定しているので,blockSize(第6引数)x blockSize の近傍領域に対してガウシアンを重みとして総和をとり,そこから C(第7引数)の値を引いた値を閾値として利用します.
このように求められた閾値を超えるピクセルは maxVal (第3引数)に,それ以外は 0 になります.

// (4)入力画像と処理結果画像を表示します.

入力画像と,処理されたそれぞれの二値化画像を表示します.また,何かキーが押されるとプログラムを終了します.

実行結果例


(左から)入力画像,固定閾値処理後の画像(BINARY,BINARY_INV)


(左から)固定閾値処理後の画像(TRUNC,TOZERO,TOZERO_INV), 適応的閾値処理後の画像

画像ピクセル値へのアクセス

No comments 03 3月 2010 Under: opencv2.0-sample

C

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

int
main (int argc, char **argv)
{
  int x, y;
  int width=1025, height=978;
  IplImage *img=0, *hsv_img=0;
  double c, f;
  
  // (1)allocate and initialize an image
  img = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
  if(img==0) return -1;
  cvZero(img);
  hsv_img = cvCloneImage(img);
  cvCvtColor(img, hsv_img, CV_BGR2HSV);
  f = cvGetTickFrequency()*1000;

  // (2)create hue-value gradation image
  c = cvGetTickCount();
  for(y=0; y<height; y++) {
    for(x=0; x<width; x++) {
      int a = img->widthStep*y+(x*3);
      hsv_img->imageData[a+0] = (x*180/width);
      hsv_img->imageData[a+1] = 255;
      hsv_img->imageData[a+2] = ((height-y)*255/height);
    }
  }
  printf("%f\n", (cvGetTickCount()-c)/f);

  cvCvtColor(hsv_img, img, CV_HSV2BGR);

  // (3)show the iamge, and quit when any key pressed
  cvNamedWindow ("Gradation", CV_WINDOW_AUTOSIZE);
  cvShowImage ("Gradation", img);
  cvWaitKey (0);

  cvDestroyWindow("Gradation");
  cvReleaseImage(&img);
  
  return 0;
}

// (1)指定サイズの画像を作成します.

関数 cvCreateImage によって,指定サイズの画像を作成します.確保されるデータ領域は,4バイト境界にアラインメントが揃えられます.このようにして作成された画像の色空間を,関数 cvCvtColor によって RGBからHSVに変換します.
また,処理時間を計測するために,1ミリ秒毎の tick 数を求めます. C++ インタフェースの getTickFrequency が 1 秒毎の tick 数を返すのに対して, cvGetTickFrequency は 1 マイクロ秒毎の tick 数を返すことに注意してください.

// (2)色相-明度のグラデーション画像を作成します.

IplImage 構造体の imageData へのポインタを取得し,色相と明度のグラデーション画像を作成します. [0] が色相 “Hue”,[1] が彩度 “Saturation”,[2] が明度 “Value” になります.

// (3)画像を表示します.

作成されたグラデーション画像を表示し,何かキーが押されるとプログラムを終了します.

C++

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

using namespace cv;

#define NO_GAP

int
main (int argc, char **argv)
{
  const int w=1025, h=978;

  // (1)create an image with specified size
#ifdef NO_GAP
  Mat img = Mat::zeros(Size(w,h), CV_8UC3);
  Mat hsv_img;
  cvtColor(img, hsv_img, CV_BGR2HSV);
#else
  Mat img = Mat::zeros(Size(w+1,h), CV_8UC3);
  cvtColor(img, img, CV_BGR2HSV);
  Rect roi_rect(0,0,w,h);
  Mat hsv_img(img, roi_rect);
#endif
  std::cout << "step:" << hsv_img.step << ", size:" << w*3 << std::endl;
  double c, f = getTickFrequency()/1000;
  int width = hsv_img.cols, height = hsv_img.rows;

  // (2a)create hue-value gradation image
  c = getTickCount();
  for(int y=0; y<height; ++y) {
    for(int x=0; x<width; ++x) {
      int a = hsv_img.step*y+(x*3);
      hsv_img.data[a+0] = (x*180/width);
      hsv_img.data[a+1] = 255;
      hsv_img.data[a+2] = ((height-y)*255/height);
    }
  }
  std::cout << (getTickCount()-c)/f << "\t";

  // (2b)create hue-value gradation image
  c = getTickCount();
  for(int y=0; y<height; ++y) {
    uchar *p = hsv_img.ptr(y);
    for(int x=0; x<width; ++x) {
      p[x*3+0] = (x*180/width);
      p[x*3+1] = 255;
      p[x*3+2] = ((height-y)*255/height);
    }
  }
  std::cout << (getTickCount()-c)/f << "\t";

  // (2c)create hue-value gradation image
  c = getTickCount();
  if(hsv_img.isContinuous()) {
    width *= height;
    height = 1;
  }
  for(int y=0; y<height; ++y) {
    uchar *p = hsv_img.ptr(y);
    for(int x=0; x<width; ++x) {
      p[x*3+0] = (x*180/width);
      p[x*3+1] = 255;
      p[x*3+2] = ((height-y)*255/height);
    }
  }
  std::cout << (getTickCount()-c)/f << "\t";
  width = hsv_img.cols;
  height = hsv_img.rows;
  
  // (2d)create hue-value gradation image
  c = getTickCount();
  for(int y=0; y<height; ++y) {
    for(int x=0; x<width; ++x) {
      Vec3b &p = hsv_img.at<Vec3b>(y,x);
      p[0] = (x*180/width);
      p[1] = 255;
      p[2] = ((height-y)*255/height);
    }
  }
  std::cout << (getTickCount()-c)/f << "\t";

  // (2e)create hue-value gradation image
  c = getTickCount();
  Mat_<Vec3b>& ref_img = (Mat_<Vec3b>&)hsv_img;
  for(int y=0; y<height; ++y) {
    for(int x=0; x<width; ++x) {
      ref_img(y, x)[0] = (x*180/width);
      ref_img(y, x)[1] = 255;
      ref_img(y, x)[2] = ((height-y)*255/height);
    }
  }
  std::cout << (getTickCount()-c)/f << "\t";

  // (2f)create hue-value gradation image
  c = getTickCount();
  vector<Mat> planes;
  split(hsv_img, planes);
  MatIterator_<uchar> it_h = planes[0].begin<uchar>();
  MatIterator_<uchar> it_s = planes[1].begin<uchar>();
  MatIterator_<uchar> it_v = planes[2].begin<uchar>();
  for(int c=0; it_h!=planes[0].end<uchar>(); ++it_h, ++it_s, ++it_v, ++c) {
    *it_h = ((c%width)*180/width);
    *it_s = 255;
    *it_v = ((height-c/width)*255/height);
  }
  merge(planes, hsv_img);
  std::cout << (getTickCount()-c)/f << "\t";

  // (2g)create hue-value gradation image (when no-gap)
#ifdef NO_GAP
  c = getTickCount();
  MatIterator_<Vec3b> it = hsv_img.begin<Vec3b>();
  for(int c=0; it!=hsv_img.end<Vec3b>(); ++it,++c) {
    int x=c%width;
    int y=c/width;
    (*it)[0] = (x*180/width);
    (*it)[1] = 255;
    (*it)[2] = ((height-y)*255/height);
  }
  std::cout << (getTickCount()-c)/f << "\t";
#endif
  std::cout << std::endl;
  
  // (3)show created gradation image
  cvtColor(hsv_img, img, CV_HSV2BGR);
  namedWindow("Garadation", CV_WINDOW_AUTOSIZE);
  imshow("Gradation", img);
  waitKey(0);
  
  return 0;
}

// (1)指定サイズの画像を作成します.

行間にギャップが存在しない領域を持つMatインスタンスを作成するには,単に Mat コンストラクタ, Mat::zeros などを利用します.逆に,ギャップを持つ画像を作成する方法の一つには,対象画像よりも横幅が大きい画像を作成し,その部分領域(部分行列,ROI)として画像を作成する方法があります.実際に,Mat.stepと width の3倍(チャンネル数倍)を比較してみると,行間にギャップが存在する事が確認できます.また, cvCreateImagecvLoadImage などの IplImage 構造体を対象とする関数が確保するデータ領域は4バイト境界にアラインメントが揃えられるので,次の用にするとギャップを持つ画像領域が確保されることになります.

Ptr<IplImage> ipl_img = cvCreateImage(cvSize(w,h), 8, 3);
cvCvtColor(ipl_img, ipl_img, CV_BGR2HSV);
Mat img, hsv_img(ipl_img);

このようにして作成された画像の色空間を,関数 cvtColor によって RGBからHSVに変換します.
また,処理時間を計測するために,1ミリ秒毎の tick 数を求めます.C インタフェースの cvGetTickFrequency が 1 マイクロ秒毎の tick 数を返すのに対して, getTickFrequency は 1 秒毎の tick 数を返すことに注意してください.

// (2a)色相-明度のグラデーション画像を作成します.

この方法は,OpenCV 1.x での処理とほぼ同じです.Mat.step を IplImage の widthStep と読み替えると,容易く理解できるでしょう.

// (2b)色相-明度のグラデーション画像を作成します.

Mat クラスには,指定行の先頭へのポインタを返すメソッド Mat.ptr() が存在します.これの引数として y (行のインデックス)を与えることで,(2a) と同様の処理が可能です.実際,OpenCV 内部でもこれらの処理はほぼ等価なものになります.

// (2c)色相-明度のグラデーション画像を作成します.

これは (2b) の処理を少し最適化したものです.あらかじめギャップの有無を調べ,ギャップが存在しない場合は,MxN の行列を 1xMN の 1行の行列として扱うことで,列のループ無くします.

// (2d)色相-明度のグラデーション画像を作成します.

これは,Mat.at メソッドを用いて,指定座標 (x,y) の参照を取得する方法です( at の引数は y, x の順になることに注意してください).この場合,関数呼び出し時にインスタンス生成のコストがかかります.

// (2e)色相-明度のグラデーション画像を作成します.

(2d) のように Mat.at を用いる代わりに,あらかじめ Mat_ クラスへキャストしたインスタンスの参照を取得し,そこに定義された()演算子を用いて指定座標 (x, y) への参照を取得します.

// (2f)色相-明度のグラデーション画像を作成します.

イテレータを利用する例です.Mat クラスのメソッドが返すイテレータは,チャンネル毎のイテレータなので,まず画像をチャンネル毎に分離してから処理を行います.また,最後に処理結果をマージする必要があります.

// (2g)色相-明度のグラデーション画像を作成します.

(無理に)イテレータを利用する例です.3チャンネルを1平面と見なし,イテレータにより処理を行いますが,当然ギャップを飛び越すことができないので,ギャップなしの画像に対してのみ用いることができます.

// (3)画像を表示します.

作成されたグラデーション画像を表示し,何かキーが押されるとプログラムを終了します.

———-

(2a) (2b) (2c) (2d) (2e) (2f) (2g)
without gap 10.317 10.524 10.164 18.940 18.973 73.622 60.040
with gap 10.000 10.261 10.221 19.191 19.108 73.484

上記の表は,処理時間を計測した結果の一例です(100試行の平均 [ms] ).(2a),(2b),(2c) は,有効桁数を考えるとほぼで同じであり,(2d)と(2e)も同様に同じ処理時間である一方,(2f),(2g)の例は極端に遅くなっています.

画像内のピクセルを横断するような処理の多くは,処理対象となる画素の空間情報(どこにその画素が位置するか)を利用するため,たとえ1チャンネル画像であったとしても,MatIterator_ を用いて処理するには向いていません.また,今回の場合のようにイテレータがクラスとして実装されている場合,ポインタを直接操作する処理と比較して時間がかかります.しかし,例えば以下のような場合では,イテレータを用いて処理を簡単に記述できます.

ある行列において,値が正である要素の合計値を求める処理を考えてみます.

・行と列のループで処理する場合

double sum=0;
int cols = M.cols, rows = M.rows;
for(int i = 0; i < rows; i++) {
  const double* Mi = M.ptr<double>(i);
  for(int j = 0; j < cols; j++) {
    sum += std::max(Mi[j], 0.);
  }
}

・イテレータを利用して処理する場合

double sum=0;
MatConstIterator_<double> it = M.begin<double>(), it_end = M.end<double>();
for(; it != it_end; ++it) {
  sum += std::max(*it, 0.);
}

このように,処理対象のインデックスを必要としない処理の場合は特に,イテレータを利用して簡潔かつ安全に処理を行うことが可能です.また,STLの任意のアルゴリズムに適用できるという利点もあります(MatIterator_ はランダムアクセスイテレータです).

実行結果例

色相-明度のグラデーション画像.色相は左から右(0-180),明度は上から下(255-0)に変化します.

複数の矩形を包含する矩形

No comments 27 2月 2010 Under: opencv2.0-sample

C

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

#define RECT_NUM (8) /** number of inner rectangles **/

int
main (int argc, char **argv)
{
  IplImage *img = 0;
  CvRNG rng = cvRNG(cvGetTickCount());
  CvRect inner[RECT_NUM], outer;
  int i;
  CvMat *rnum = cvCreateMat(RECT_NUM*2, 1, CV_32SC2);

  // (1)allocate and initialize an image
  img = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 3);
  cvRectangle(img, cvPoint(0,0), cvPoint(640,480), CV_RGB(100,100,100), CV_FILLED, 8, 0);

  // (2)generate rundom coordinates within a center space
  cvRandArr(&rng, rnum, CV_RAND_UNI, cvScalar(img->width/8, img->height/8, 0, 0), cvScalar(img->width*7./8, img->height*7./8,0,0));

  // (3)draw rectangles based on generated coordinates
  for(i=0; i<RECT_NUM; i++) {
    int x1 = rnum->data.i[i*4+0];
    int y1 = rnum->data.i[i*4+1];
    int x2 = rnum->data.i[i*4+2];
    int y2 = rnum->data.i[i*4+3];
    CvPoint tl = cvPoint(MIN(x1, x2), MIN(y1,y2));
    CvPoint br = cvPoint(MAX(x1, x2), MAX(y1,y2));
    cvRectangle(img, tl, br, CV_RGB(255,200,200), 2, 8, 0);
    inner[i] = cvRect(tl.x, tl.y, br.x-tl.x, br.y-tl.y);
  }

  // (4)find a minimum rectangle including all rectangles
  outer = inner[0];
  for(i=1; i<RECT_NUM; i++)
    outer = cvMaxRect(&outer, &inner[i]);
  cvRectangle(img, cvPoint(outer.x, outer.y),
	      cvPoint(outer.x+outer.width, outer.y+outer.height), CV_RGB(255,0,0), 3, 8, 0);
  
  // (5)show the iamge, and quit when any key pressed
  cvNamedWindow ("BoundingRect", CV_WINDOW_AUTOSIZE);
  cvShowImage ("BoundingRect", img);
  cvWaitKey (0);

  cvDestroyWindow("BoundingRect");
  cvReleaseImage(&img);
  
  return 0;
}

// (1)画像領域を確保し,それを初期化します.

サイズ 640×480 の画像領域を確保し,それを cvRectangle でグレー(100,100,100)に塗りつぶします.

// (2)画像中心領域に収まるランダムな座標を生成します.

cvRandArr を用いて,上下左右に 1/8 の余白を持つ中心領域に収まるような座標を生成します.

// (3)生成された座標を用いて,矩形を描画します.

生成された乱数座標を矩形の対角座標とすることでランダムな矩形を生成し,それを描画します. cvRectangle は,左上,右下の座標を必要とするので, MIN, MAX マクロを用いて適切な座標を利用します.

// (4)すべての矩形を包含する最小の矩形を求めます.

関数 cvMaxRect を用いて,生成された矩形を包含する最小の矩形を求めます. cvRectangle により,その矩形を赤色で描画します.

// (5)画像を表示します.

すべての矩形が描画された画像を表示し,何かキーが押されるまで待ちます.

C++

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

using namespace cv;

int
main (int argc, char **argv)
{
  // (1)allocate and initialize an image
  Mat img(Size(640, 480), CV_8UC3, Scalar(100, 100, 100));

  // (2)generate rundom coordinates within a center space
  const int rect_num = 8; /** number of inner rectangles **/
  Mat rnum(Size(rect_num*2,1), CV_32SC2);
  RNG(getTickCount()).fill(rnum, RNG::UNIFORM, Scalar(img.cols/8, img.rows/8), Scalar(img.cols*7./8, img.rows*7./8));
  
  // (3)draw rectangles based on generated random coordinates
  vector<Rect> inner;
  MatIterator_<Vec2i> itp = rnum.begin<Vec2i>();
  for(; itp!=rnum.end<Vec2i>(); itp+=2) {
    Point a((*itp)[0], (*itp)[1]), b((*(itp+1))[0], (*(itp+1))[1]);
    rectangle(img, a, b, Scalar(200,200,255), 2);
    inner.push_back(Rect(a, b));
  }

  // (4)find a minimum rectangle including all rectangles
  vector<Rect>::iterator itr = inner.begin();
  Rect outer = *itr;
  for(++itr; itr!=inner.end(); ++itr)
    outer |= *itr;
  rectangle(img, outer.tl(), outer.br(), Scalar(0,0,255), 3);

  // (5)show the iamge, and quit when any key pressed
  namedWindow("img", CV_WINDOW_AUTOSIZE);
  imshow("img", img);
  waitKey(0);

  return 0;
}

// (1)画像領域を確保し,それを初期化します.

サイズ 640×480 のグレー(100,100,100)で塗りつぶされた画像領域を確保します.

// (2)画像中心領域に収まるランダムな座標を生成します.

指定範囲内(上述のCの例と同様)に一様分布する疑似乱数で,行列を埋めます.
ここでは,低レベルな関数 RNG を利用して行列を埋めていますが, randu を利用しても同様のことが可能です.ただし,randu は,常に同じシードを利用するデフォルト乱数生成器を利用するため,常に同じ順列の乱数が生成されます.実行の度に異なる乱数を使用したい場合には,例えばここで用いているように getTickCount を利用して乱数を生成します.このサンプルでは,上下左右に 1/8 の余白を持つ中心領域に収まるような座標が生成されます.

// (3)生成された座標を用いて,矩形を描画します.

生成された乱数座標を矩形の対角座標とすることでランダムな矩形を生成し,それを描画します. cvRectangleとは異なり, rectangle はパラメータとして入力された座標が左上,右下の順ではなくとも適切な矩形を描画するので,座標をそのまま利用します.

// (4)すべての矩形を包含する最小の矩形を求めます.

Rect クラスのオーバーライドされた |= 演算子を用いて,すべての矩形を含む矩形(すべての矩形の論理和)を求めます.関数 rectangle により,その矩形を赤色で描画します.

// (5)画像を表示します.

すべての矩形が描画された画像を表示し,何かキーが押されるまで待ちます.

実行結果例


ピンク色がランダムに生成された矩形,赤色がそれらを含む最小の矩形(傾きなし)を表します.

Next