API介绍:
int createTrackbar(const String& trackbarname, const String& winname,
int* value, int count,
TrackbarCallback onChange = 0,
void* userdata = 0);
/*trackbarname:滑块名
winname:窗口名
value:滑块当前位置,这个参数的值可以被回调函数使用。
count:滑块总长度
onChange:指向回调函数的指针,每次滑块移动时都会调用此函数
userdata:用户自定义数据,使用它可以避免使用全局变量,这个变量大部分博客都没有使用,直接将回调函数用到的参数设为全局变量,
本文前面部分也没有使用此参数,在后面部分会有使用此参数的例子作和对比*/
需要进行的操作全在回调函数onChange里进行。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
用户可自定义此函数。createTrackbar这个函数主要作用是创建滑块,然后在滑块每次移动的时候调用onChange这个函数。onChange(int ,void*)
第一个变量为滑动条对应的位置,就是回调函数传入的第三个参数。
第二个为用户变量的指针。
下面举几个例子。
图像融合例子:
#include <opencv2/opencv.hpp>
using namespace cv;
#define window_name "线性混合"
//变量声明
const int max_value = 100; //滑条的最大值
int value; //滑动条对应的变量
//声明存储图像的变量,这里为了方便定义为全局变量,不过此做法会浪费内存
Mat image1;
Mat image2;
//响应滑动条的回调函数
//第一个变量为滑动条对应的位置,第二个为用户变量的指针,这里我们把这些都定义为了全局变量,因此没有使用到这两个参数。
void on_trackbar(int, void *)
{
Mat dst;
double alpha1; //第一张图的权重
double alpha2; //第二张图的权重
//求出当前alpha值对于最大值的比例
alpha1 = (double)value / max_value;
//求出第二张图片的权重
alpha2 = (1.0 - alpha1);
//线性混合
addWeighted(image1, alpha1, image2, alpha2, 0.0, dst);
//显示效果图
imshow(window_name, dst);
}
int main()
{
image1 = imread("c:\\users\\45374\\desktop\\一些经典算法的代码整理\\code\\img\\3.png");
image2 = imread("c:\\users\\45374\\desktop\\一些经典算法的代码整理\\code\\img\\4.png");
if (!image1.data)
{
printf("第一张读取失败\n");
return -1;
}
if (!image2.data)
{
printf("第二张读取失败\n");
return -1;
}
//设置滑动条的初始位置
value = 50;
//创建窗体
namedWindow(window_name);
//在创建窗体中创建一个滑动条
char tranckbarname[50] = "透明值100";
createTrackbar(tranckbarname, window_name, &value, max_value, on_trackbar);
on_trackbar(value, 0);//在滑块没有移动时手动调用一次回调函数,不然最开始不会显示图片。
//后面两个参数可以随便给,因为on_trackbar函数中并没有用到这两个参数只是为了符合createTrackbar参数调用的格式
waitKey();
return 0;
}
效果如下:
关于trackbar在形态学操作中的例子以及一张图创建多个trackbar的例子见我的另一篇博客:这里。
用户变量void* userdata的使用
在我们上面的程序中,并没有使用到void* userdata这个参数,这是因为我们将变量都定义为了全局变量。但是大家知道,如果当程序过大时,全局变量过多会造成很大的内存消耗,如果我们要想避免全局变量的使用,这时候就需要借助之前我们没有使用的那个参数void* userdata。
这里我们定义一个类:
class my_all {
public:
Mat image1;
Mat image2;
int max_value = 100;
};
这样可以使我们需要在回调函数中用到的几个参数一起传入回调函数。具体代码如下:
#include "pch.h"
#include <opencv2/opencv.hpp>
using namespace cv;
#define window_name "线性混合"
class my_all {
public:
Mat image1;
Mat image2;
int max_value = 100;//滑条最大位置
};
//响应滑动条的回调函数
//第一个变量为滑动条对应的位置,第二个为用户变量的指针,这里我们把这些都定义为了全局变量,因此没有使用到这两个参数。
void on_trackbar(int value, void *SRC)
{
Mat dst;
double alpha1; //第一张图的权重
double alpha2; //第二张图的权重
my_all src = *(my_all*)SRC;
int max_value = src.max_value;
//求出当前alpha值对于最大值的比例
alpha1 = (double)value / max_value;
//求出第二张图片的权重
alpha2 = (1.0 - alpha1);
Mat image1 = src.image1;
Mat image2 = src.image2;
//线性混合
addWeighted(image1, alpha1, image2, alpha2, 0.0, dst);
//显示效果图
imshow(window_name, dst);
}
int main()
{
Mat image1 = imread("c:\\users\\45374\\desktop\\一些经典算法的代码整理\\code\\img\\3.png");
Mat image2 = imread("c:\\users\\45374\\desktop\\一些经典算法的代码整理\\code\\img\\4.png");
if (!image1.data)
{
printf("第一张读取失败\n");
return -1;
}
if (!image2.data)
{
printf("第二张读取失败\n");
return -1;
}
//设置滑动条的初始位置
int value = 50;//滑条当前位置
int max_value = 100;
my_all SRC;
SRC.image1 = image1;
SRC.image2 = image2;
SRC.max_value = max_value;
//通过类将回调函数需要的几个数据打包然后作为指针传入createTrackbar
//创建窗体
namedWindow(window_name);
//在创建窗体中创建一个滑动条
char tranckbarname[50] = "透明值100";
createTrackbar(tranckbarname, window_name, &value, max_value, on_trackbar, &SRC);
on_trackbar(value, &SRC);//在滑块没有移动时手动调用一次回调函数,不然最开始不会显示图片。
waitKey();
return 0;
}
从上面的代码中可以看出,此次我们并没有定义全局变量,而是在main函数里定义了image1、image2、max_value,然后将它们打包为一个类通过void* userdata传入回调函数。
此外还有一个变量:滑条当前位置value。这个参数已经通过createTrackbar传入,可以在onChange(int ,void*)中通过int value中来使用,int value这个过程相当于给传入的滑条当前位置的参数取个名字。此时value的值已经通过createTrackbar的第三个参数传入。避免了value定义为全局变量。