当评价配准的结果是否可靠的时候,可以使用两张叠加的图片进行,可以使用:
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;
};
分别是两张图片的数据,当前滑块的位置,所有的参考点坐标。写好滑块滑动的回调函数,传入的userdata
是void*
类型的指针,首先转换为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操作,并且往结构体里面添加东西的时候会比较方便。