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

# 背景
[C++版OpenCVでトラックバーを表示してみた](https://qiita.com/aomidro/items/3e3449fde924893f18ca) で表示する画像とトラックバーの変数はグローバル変数で宣言していた。

```cpp
#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`メソッドの６番目の引数である`userdata`で引数にできそうだ。で、グローバル変数を使わずにローカル関数内で宣言した変数でトラックバーが動かせないか確かめてみました。


# Doc
[cv::createTrackbar](http://opencv.jp/opencv-2.1/cpp/user_interface.html#cv-createtrackbar) にある引数 `onChange` `userdata` は 下記の通りに書かれています。

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

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

# 型変換の流れ
- 参照元でvoid型アドレス変数にキャスト変換して関数に渡す

```c++
cv::createTrackbar(..., ..., ..., ..., ..., (void*)(&img));
```
- 参照先で引数の型がvoid型アドレス変数になっているので元に戻す 

```c++
void nsize_trackbar( int size, void* userdata)
{
   cv::Mat *srcimg;
   //型を元に戻してアドレスを渡す
   srcimg = (cv::Mat*)(userdata);
```

しばらくPythonしているとポインタ何それおいしいの:weary: となってしまいましたが[第３３章　ポインタ天国２](http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01033.html) と [第３４章　ポインタ天国３](http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01034.html) みると良いかも。



# 開発

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

```c++
#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](http://opencv.jp/cookbook/opencv_io.html#id13) が少し参考になります。

```c++
#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`第１引数の名称と第３引数でパラメータ値のアドレスを渡すことで紐づけているっぽいので別のパラメータでトラックバーの値がセットされることなく重複しないようになっています。

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

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

# 参考になりそうなリンク
- [In OpenCV createTrackbar function what is the 6th paramter(User data) how it will be useful.(Please find the parameters in the explanation) [closed]](https://stackoverflow.com/questions/24218890/in-opencv-createtrackbar-function-what-is-the-6th-paramteruser-data-how-it-wil)
- [Trackbar pass variable - opencv form Q&A](https://answers.opencv.org/question/91462/trackbar-pass-variable/)
- [トラックバーを追加する - cookbook](http://opencv.jp/cookbook/opencv_io.html#id13)
- [第３３章　ポインタ天国２](http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01033.html)
- [第３４章　ポインタ天国３](http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01034.html)