Help us understand the problem. What is going on with this article?

C++版OpenCVでcreateTrackbar第6引数userdataを使ってコールバック関数に参照させてみた

背景

C++版OpenCVでトラックバーを表示してみた で表示する画像とトラックバーの変数はグローバル変数で宣言していた。

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/xphoto.hpp>

cv::Mat src = cv::imread("./mikan.jpg");
int nsize_slider = 2;
int r_slider = 1;

...

これではスコープの範囲が広くなりすぎるのでcreateTrackbarメソッドかcallbackメソッドで引数にて処理したいと思ったりします。調べていくとcreateTrackbarメソッドの6番目の引数であるuserdataで引数にできそうだ。で、グローバル変数を使わずにローカル関数内で宣言した変数でトラックバーが動かせないか確かめてみました。

Doc

cv::createTrackbar にある引数 onChange userdata は 下記の通りに書かれています。

  • onChange – スライダの位置が変更されるたびに呼び出される関数のポインタ.この関数のプロトタイプは, void Foo(int,void*); となり,最初の引数はトラックバーの位置,2番目引数はユーザデータ(次の引数説明を参照してください)を表します.このコールバックがNULLポインタの場合,コールバック関数は呼び出されませんが, value だけは更新されます
  • userdata – コールバック関数に渡されることになるユーザデータ.グローバル変数に頼らずにトラックバーイベントを扱うために利用されます

この void Foo(int,void*) がコールバック関数にあたるのですが 第2引数void* の型は固定です。で、第6引数userdata は参照元なのですがここでvoid*型に変換しないとエラーになります。*がついているのはアドレス変数です。

型変換の流れ

  • 参照元でvoid型アドレス変数にキャスト変換して関数に渡す
cv::createTrackbar(..., ..., ..., ..., ..., (void*)(&img));
  • 参照先で引数の型がvoid型アドレス変数になっているので元に戻す
void nsize_trackbar( int size, void* userdata)
{
   cv::Mat *srcimg;
   //型を元に戻してアドレスを渡す
   srcimg = (cv::Mat*)(userdata);

しばらくPythonしているとポインタ何それおいしいの:weary: となってしまいましたが第33章 ポインタ天国2第34章 ポインタ天国3 みると良いかも。

開発

まず表示画像のみを引数にした場合のコードを表示します。

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/xphoto.hpp>


int nsize_slider = 2;
int r_slider = 1;

void nsize_trackbar( int size, void* userdata)
{
   cv::Mat dst;
   cv::Mat *srcimg;
   srcimg = (cv::Mat*)(userdata);

   cv::xphoto::oilPainting(*srcimg, dst, size, r_slider, cv::COLOR_BGR2Lab);
   cv::imshow("oil painting effect", dst);
}

int main(int argc, const char* argv[]) {

   cv::Mat img= cv::imread("./mikan.jpg");

   if (img.empty()) {
      std::cout << "read error.\n";
      return -1;
   }

   cv::namedWindow("oil painting effect", cv::WINDOW_AUTOSIZE);

   cv::createTrackbar("neighbouring size", "oil painting effect", &nsize_slider, 20, nsize_trackbar, (void*)(&img));
   cv::setTrackbarPos("neighbouring size", "oil painting effect", 5);
   cv::setTrackbarMin("neighbouring size", "oil painting effect", 2);

   nsize_trackbar(2, (void*)(&img));

   cv::waitKey(0);
   img.release();
   cv::destroyAllWindows();

   return 0;
}

これで一つのトラックバーを表示することができます。

では、複数の画像やパラメータをまとめて引数にしたい、となるとクラス化するのが良さげです。OpenCV-CookBook が少し参考になります。

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/xphoto.hpp>


class DynamicValue{
public:
   cv::Mat image = cv::imread("./mikan.jpg");
   int nsize = 2;
   int ratio = 1;
};

void trackbar_callback( int size, void* userdata)
{
   cv::Mat dst;
   DynamicValue *dvalue;
   dvalue = (DynamicValue*)(userdata);

   cv::xphoto::oilPainting(dvalue->image, dst, dvalue->nsize, dvalue->ratio, cv::COLOR_BGR2Lab);
   cv::imshow("oil painting effect", dst);
}

int main(int argc, const char* argv[]) {

   DynamicValue dyn;

   cv::namedWindow("oil painting effect", cv::WINDOW_AUTOSIZE);

   cv::createTrackbar("neighbouring size", "oil painting effect", &dyn.nsize, 20, trackbar_callback, (void*)(&dyn));
   cv::setTrackbarPos("neighbouring size", "oil painting effect", 5);
   cv::setTrackbarMin("neighbouring size", "oil painting effect", 2);


   cv::createTrackbar("dynRatio", "oil painting effect", &dyn.ratio, 10, trackbar_callback, (void*)(&dyn) );
   cv::setTrackbarPos("dynRatio", "oil painting effect", 3);
   cv::setTrackbarMin("dynRatio", "oil painting effect", 1);

   trackbar_callback(2, (void*)(&dyn));

   cv::waitKey(0);
   cv::destroyAllWindows();

   return 0;
}

コールバック関数ですが一つのトラックバーごとに使っていたのですが、クラス化することで複数の変数を呼び出すことができるでさらにトラックバーが必要になっても関数は一つですみそうです。
createTrackbar第1引数の名称と第3引数でパラメータ値のアドレスを渡すことで紐づけているっぽいので別のパラメータでトラックバーの値がセットされることなく重複しないようになっています。

前よりもよくまとまったと思います。:robot:

おわりに

cookbook は画像処理に必要な最低限のメソッドの解説と例題が多くあり、公式にないものも含まれていているので結構参考になるのですが、バージョンが2系でちょっと古いです。4系で登場した解説・例題があるといいんですけど:sweat_smile:
Python開発者がC++より多くなったせいでしょうか。

参考になりそうなリンク

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした