背景
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しているとポインタ何それおいしいの となってしまいましたが第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引数でパラメータ値のアドレスを渡すことで紐づけているっぽいので別のパラメータでトラックバーの値がセットされることなく重複しないようになっています。
前よりもよくまとまったと思います。
おわりに
cookbook は画像処理に必要な最低限のメソッドの解説と例題が多くあり、公式にないものも含まれていているので結構参考になるのですが、バージョンが2系でちょっと古いです。4系で登場した解説・例題があるといいんですけど
Python開発者がC++より多くなったせいでしょうか。