RobotVision勉強会:第4回 マウスイベント処理

 

この記事は私が主催しているRobotVision勉強会の内容メモです。OpenCVの内容については下のサイトやOpenCV3.2.0のドキュメントを参考にしています。なお、開発環境はUbuntu16.04、OpenCV3.2.0です。第4回はマウスのイベント処理です。以下のサンプルプログラムではマウスで画像中の矩形領域を設定し、そこに含まれている画素の赤、緑、青成分の最小、最大値を取得します。
なお、マウスでボックスを描画し、その座標を取得することに関しては、Learning OpenCV 3 (著者: Adrian Kaehler、Gary Bradski, 出版社:O’REILLY)のExample9-2 P214,215のコードをもとに作成しました。Learning OpenCV 3は990ページもある大型本で前著より約400ページも増量されとても参考になります。お勧めです。ペーパーバックス版を購入したましたが、これだけ分厚と持ち運びが不便なのでKindle版が良いかもしれませんね。

1.サンプルコード

// Robot Vision勉強会 sample4.cpp
// 2017-11-09
// 
// マウスでドラッグしたボックス領域に含まれる画素の
// 赤、緑、青成分の最小、最大値を取得するプログラム。

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

cv::Rect box;

bool drawing_box = false; 

// ボックスの始点、終点座標 
int start_x = 0, start_y = 0, end_x = 0, end_y =0;

// ボックスの描画
void drawBox(cv::Mat* img, cv::Rect box){
  cv::rectangle(*img, box.tl(), box.br(),cv::Scalar(0, 0, 255));
}

// コールバック関数
void myMouseCallback(int event, int x, int y, int flags, void* param){
  cv::Mat* image = static_cast<cv::Mat*>(param);

  switch (event){
  case cv::EVENT_MOUSEMOVE:
    if (drawing_box){
      box.width =  x - box.x;
      box.height = y - box.y;
    }
    break;
  case cv::EVENT_LBUTTONDOWN:
    drawing_box = true;
    start_x = x;
    start_y = y;
    box = cv::Rect(x, y, 0, 0);
    break;
  case cv::EVENT_LBUTTONUP:
    drawing_box = false;
    if (box.width < 0){
      box.x += box.width;
      box.width *= -1;
    }
    if (box.height < 0){
      box.y += box.height;
      box.height *= -1;
    } 
    end_x = x;
    end_y = y;
    drawBox(image, box);
    break;
  }
}

int main(void)
{
  cv::Mat src;

  // trueになると閾値内のピクセルだけを赤色に変更
  bool paint_flag = false;  
  // trueになると閾値内のピクセルを赤色、その他のピクセルを白に変更
  bool color_flag = false;
  
  std::string WINDOW_NAME = "Extract color";
  box = cv::Rect(-1, -1, 0, 0);

  // 画像のロード
  src = cv::imread("winkit2.jpg", cv::IMREAD_COLOR);
  if(src.empty()) {
    std::cerr << "Failed to open image file." << std::endl;
    return -1;
  }

  int height = src.rows;       // 画像の高さ[pixel]
  int width  = src.cols;       // 画像の幅[pixel]
  int step   = src.step;       // 1行のチャンネル総数
  int c      = src.channels(); // チャンネル数   

  
  cv::Mat tmp = src.clone();

  // ウィンドウの生成
  cv::namedWindow(WINDOW_NAME, CV_WINDOW_AUTOSIZE);

  // コールバック関数の設定
  cv::setMouseCallback(WINDOW_NAME, myMouseCallback, (void *)&src);

  while (true){
    src.copyTo(tmp);

    // マウスの左クリックを離すまでボックスを一時的に描画
    if (drawing_box) {
      drawBox(&tmp, box);
    }

    cv::imshow(WINDOW_NAME, tmp);

    int blue_min  = 999, blue_max  = -999;
    int green_min = 999, green_max = -999;
    int red_min   = 999, red_max   = -999;

    // ボックス領域の各色成分の最小、最大値を取得
    for (int y =start_y; y < end_y; y++) {
      for (int x = start_x; x < end_x; x++) { int b = src.data[y* step + x * c + 0]; if (blue_min > b) blue_min = b;
	if (blue_max < b) blue_max = b; int g = src.data[y* step + x * c + 1]; if (green_min > g) green_min = g;
	if (green_max < g) green_max = g; int r = src.data[y* step + x * c + 2]; if (red_min > r) red_min = r;
	if (red_max < r) red_max = r;
      }
    }

    std::cout << "blue : min=" << blue_min  << " max=" << blue_max  << std::endl;
    std::cout << "green: min=" << green_min << " max=" << green_max << std::endl;
    std::cout << "red  : min=" << red_min   << " max=" << red_max   << std::endl;

    // 取得した閾値の範囲に入る画素を赤く塗る
    for (int y =0; y < height; y++) {
      for (int x = 0; x < width; x++) {
	int b = tmp.data[y* step + x * c + 0];
	int g = tmp.data[y* step + x * c + 1];
	int r = tmp.data[y* step + x * c + 2];
	if (blue_min <= b && b <= blue_max && green_min <= g && g <= green_max
	    && red_min <= r && r <= red_max) {
	  if (paint_flag == true) {
	    src.data[y* step + x * c + 0] = 0;
	    src.data[y* step + x * c + 1] = 0;
	    src.data[y* step + x * c + 2] = 255;
	  }
	}
	else {
	  if (color_flag == true) {
	    src.data[y* step + x * c + 0] = 255;
	    src.data[y* step + x * c + 1] = 255;
	    src.data[y* step + x * c + 2] = 255;
	  }
	}
      }
    }
      
    
    int c = cv::waitKey(1);
    switch (c)  {
    case 'p':
      paint_flag = true;
      break;
    case 'c':
      color_flag = true;
      break;
    case 32: // space 
       paint_flag = color_flag = false;
       start_x = start_y = end_x = end_y = 0;
       box.x = box.y = box.width = box.height = 0;
       src = cv::imread("winkit2.jpg", cv::IMREAD_COLOR);
       break;
    }
  }
  return 0;
}

2.ハンズオン

  • このソースコードsample4をダウンロードして次のコマンドで解凍、コンパイルして実行しよう。マウスでドラッグすると領域が選択され、そこに含まれているピクセルの最小、最大値が閾値になります。キーボードからpを入力すると、閾値の範囲になるピクセルが赤く塗られます。cを入力すると赤く塗られたピクセル以外が白くなり、画像中のどの領域がその閾値の範囲にあるかよくわかります。広い領域を選択すると画像中のいろいろなピクセルがその閾値内に入るので、どの領域を選択するかが重要です。
    • $  tar xvzf sample4.tgz
    • $  cd sample4
    • $  cmake .
    • $ make
    • ./sample4
  • ソースコードのAPIの意味を調べてプログラムを理解しよう。
  • このサンプルプログラムでは選択した領域の各画素の最小・最大値を閾値としているので、領域がうまく分割されません。第2回の閾値をスライドバーで変化させるプログラムとこのサンプルを組み合わせて使い勝手をよくしよう!
  • このサンプルはRGB表色系を使っていますが、明るさの変化に弱いです。これをYUV表色系に変更することでロバストにしよう!

コメントを残す

メールアドレスが公開されることはありません。