当评价配准的结果是否可靠的时候,可以使用两张叠加的图片进行,可以使用:

cv::addWeighted(mat1, weight, mat2, 1 - weight, 0, dst);

的形式,然而如果想要使用滑块来动态调整weight的时候,如果直接使用OpenCV给的示例的话,需要使用道很多的全局变量,赵成代码比较混乱。这里使用userdata进行数据传输,实现两个图片的数据传入回调函数,然后进行带权重的相加。

使用memcpy的形式

由于滑动条的回调函数只有两个参数(int, void*),其中int是参数信息,void*是数据信息,所以使用一片内存地址分别存入两个图片的数据,然后在CallBack中将数据取出来。

static void addWeightOpenCVCallBack(int pos, void *userdata)
{
	// 更加图像大小创建空数据
    const unsigned int size = 1024;
    const unsigned int matBits = sizeof(unsigned char) * size * size;
    cv::Mat mat1 = cv::Mat::zeros(size, size, CV_8UC1);
    cv::Mat mat2 = cv::Mat::zeros(size, size, CV_8UC1);
    cv::Mat dst = cv::Mat::zeros(size, size, CV_8UC1);
	// 把userdata内存分别赋值到两个图片
    memcpy(mat1.data, userdata, matBits);
    memcpy(mat2.data, (unsigned char*)userdata + matBits, matBits);
	// 将int参数转换为float,然后带权重相加
    float weight = (float)pos / 100.0F;
    cv::addWeighted(mat1, weight, mat2, 1 - weight, 0, dst);
    cv::imshow("registed", dst);
}

测试函数:

void main()
{
    cv::Mat data1 = cv::imread("0.jpg",0); //读取为灰度图像
    cv::Mat data2 = cv::imread("1.jpg",0);
    unsigned char* p = new unsigned char[1024 * 1024 * 2]; // 申请两个图片大小的动态内存

	// 将两张图的数据拷贝到动态内存中
    memcpy(p, data1.data, sizeof(unsigned char) * 1024 * 1024);
    memcpy(p + sizeof(unsigned char) * 1024 * 1024, data2.data, sizeof(unsigned char) * 1024 * 1024);

    cv::namedWindow("registed", cv::WINDOW_NORMAL);
    int pos = 0;
	// 创建一个滑块,把p作为用户参数传入
    cv::createTrackbar("weight", "registed", &pos, 100, addWeightOpenCVCallBack, (void*)p);
    addWeightOpenCVCallBack(0, p);
    cv::waitKey(0);
    delete[] p;
}

这种方式对于图片的格式和大小十分敏感,需要根据图片信息调整回调函数里面的参数,当userdata里面还需要塞进去更多东西的时候会显得比较麻烦。

使用结构体

由于void*可以转化为任意指针,所以考虑在传入参数之前构建一个结构体,并将其指针转换为void*指针传入回调函数,在回调函数内部再将其转换回原来类型的指针。这样也可以不用暴露全局变量。

这里想要完成一个功能:对两张图片进行叠加,滑块可以控制叠加的权重;同时支持鼠标左键点击创建一个参考点,方便观察配准的结果。

定义一个结构体:

struct ImagesData
{
	cv::Mat img1;
	cv::Mat img2;
	int current_pos;
	std::vector<cv::Point> points;
};

分别是两张图片的数据,当前滑块的位置,所有的参考点坐标。写好滑块滑动的回调函数,传入的userdatavoid*类型的指针,首先转换为ImagesData*类型的指针,就可以访问成员变量进行加权和参考点绘制。

static void addWeightedOpencvCallback(int pos, void* userdata) 
{
	ImagesData* data = reinterpret_cast<ImagesData*>(userdata);
	data->current_pos = pos;
	float weight = (float)pos / 100;
	cv::Mat pre;
	cv::addWeighted(data->img1, (float)pos / 100, data->img2, (1.0 - weight), 0, pre);
	for (auto p : data->points)
	{
		cv::circle(pre, p, 3, cv::Scalar(0, 0, 255), -1);
	}
	cv::imshow("add", pre);
}

鼠标点击事件的回调函数,将void* userdata数据转换为ImagesData*类型,然后将点击的点坐标添加到参考点vector里面去,调用滑动块回调函数进行绘制。

static void onMouse(int event, int x, int y, int flags, void* userdata)
{
	if (event == cv::EVENT_LBUTTONDOWN)
	{
		ImagesData* data = reinterpret_cast<ImagesData*>(userdata);
		data->points.push_back(cv::Point(x, y));
		int pos = data->current_pos;
		addWeightedOpencvCallback(pos, userdata);
	}
}

测试函数为:

int main()
{
	cv::Mat img1 = cv::imread("img1.jpg");
	cv::Mat img2 = cv::imread("Window 0.jpg");
	ImagesData data;
	
	data.img1 = img1;
	data.img2 = img2;
	int pos = 0;
	cv::namedWindow("add", cv::WINDOW_NORMAL);
	cv::createTrackbar("weight", "add", &pos, 100, addWeightedOpencvCallback, (void*)(&data));
	cv::setMouseCallback("add", onMouse, (void*)(&data));
	addWeightedOpencvCallback(0, (void*)(&data));
	cv::waitKey(0);
}

这种方式避免了memcpy操作,并且往结构体里面添加东西的时候会比较方便。