OpenCV 支持有限的界面编程,主要针对窗口、一些控件和一些鼠标事件等,比如滑块。有了这些窗口和控件,可以更好地展现图像并调节图像的一些参数。这些界面编程主要由 High-level GUI(高级图形用户界面)模块支持。
1.新建窗口并显示
在 High-Level GUI 模块中,用于新建窗口的函数是 nameWindow,同时可以指定窗口的类型。该函数声明如下:
cpp
void namedWindow(const string& winname, int flags);
其中参数 winname 表示新建的窗口的名称,自己随便取;flags 表示窗口的标识,一般默认为WINDOW_AUTOSIZE,WINDOW_AUTOSIZE 表示窗口大小自动适应图片大小,并且不可手动更改,WINDOW_NORMAL 表示用户可以改变这个窗口大小,WINDOW_OPENGL 表示窗口创建的时候支持 OpenGL。
在 High-Level GUI 模块中,用于显示窗口的函数是 imshow,该函数声明如下:
c
void imshow(const string& winname, InputArray image);
其中参数 winname 表示显示的窗口名,可以使用 cv::namedWindow 函数创建窗口,若不创建,则 imshow 函数将自动创建;image 表示窗口中需要显示的图像。
根据图像的深度,imshow 函数会自动对其进行缩放,规则如下:
(1)如果图像数据类型是 8U(8 位无符号),就直接显示。
(2)如果图像数据类型是 16U(16 位无符号)或 32S(32 位有符号整数),imshow 函数内部就会自动将每个像素值除以 256 并显示,即将原图像素值的范围由(0-255X256)映射到0\~255。
(3)如果图像数据类型是 32F(32 位浮点数)或 64F(64 位浮点数),imshow 函数内部就会自动将每个像素值乘以 255 并显示,即将原图像素值的范围由0~1映射到0~255(注意:原图像素值必须归一化)。
需要注意的是,imshow 之后必须有 waitKey 函数,否则显示窗口将一闪而过,不会驻留屏幕。waitKey 函数声明如下:
cpp
int cv::waitKey(int delay=0);
其中参数 delay 表示延时值,单位为毫秒,默认是 0,表示一直等待,直到用户按键。如果在指定时间之前没有按任何键,就返回-1,如果在指定时间之前按键,就返回按键的对应值。
当 delay≤0 时,函数 waitKey 无限地等待一个按键事件,或当 delay 是正数的时等待 delay 毫秒。由于操作系统在切换线程之间有一个最短的时间间隔,因此该函数不会等待确切(精确)的延迟毫秒,它将至少等待延迟毫秒,这取决于当时计算机上运行的其他操作。如果在指定时间之前没有按任何键,就返回按键的代码或-1。
注意,此函数是 HighGUI 模块中唯一可以获取和处理事件的方法,因此需要定期调用它以进行正常的事件处理,除非 HighGUI 在负责事件处理的环境中使用。另外,只有至少创建了一个 HighGUI 窗口并且该窗口处于活动状态,该函数才起作用。如果有几个 HighGUI 窗口,其中任何一个都可以是活动的。
示例程序:
cpp
#include <iostream>
#include <opencv2/highgui/highgui_c.h>
#include <opencv2\opencv.hpp>
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库
using namespace cv;
int main()
{
Mat img;
img = imread("test.jpg", 1);//参数 1:图片路径。参数 2:显示原图
namedWindow("window1", WINDOW_AUTOSIZE);
imshow("window1", img);//在"窗口 1"这个窗口输出图片
waitKey(5000);//等待 5 秒,程序自动退出。改为 0,不自动退出
return 0;
}

2.单窗口显示多幅图像
前面我们新建的窗口中只显示了一幅图,有时在某些场景下,比如有多个视频图像,如果一个视频图像显示在一个窗口中,就会因为窗口过多而显得凌乱。此时需要一个窗口能显示多个视频图像。要达到这个效果,原理并不复杂,只需要调整每个视频的尺寸大小为窗口的一部分,这样多幅图像组合起来正好占满一个窗口。
在 OpenCV 中,我们可以综合利用坐标变换与 Rect 区域提取来实现单窗口显示多幅图像。首先根据输入图像个数与尺寸确定输入源图像小窗口的构成形态,然后设定每个图像小窗口的具体构成,包括边界、间隙等,最后根据小窗口确定输出图像的尺寸,利用缩放函数 resize 进行图像缩放,完成单窗口下多幅图像的显示。
cpp
#include<iostream>
#include <opencv2/highgui/highgui_c.h>
#include <opencv2\opencv.hpp>
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库
using namespace cv;
using namespace std;
void showManyImages(const vector<Mat>& srcImages, Size imageSize)
{
int nNumImages = srcImages.size();
Size nSizeWindows;
if (nNumImages > 12)
{
cout << "no more than 12 images" << endl;
return;
}
//根据图像序列数量来确定分割小窗口的形态
switch (nNumImages)
{
case 1:nSizeWindows = Size(1, 1); break;
case 2:nSizeWindows = Size(2, 1); break;
case 3:
case 4:nSizeWindows = Size(2, 2); break;
case 5:
case 6:nSizeWindows = Size(3, 2); break;
case 7:
case 8:nSizeWindows = Size(4, 2); break;
case 9:nSizeWindows = Size(3, 3); break;
default:nSizeWindows = Size(4, 3);
}
//设置小图像尺寸、间隙、边界
int nShowImageSize = 200;
int nSplitLineSize = 15;
int nAroundLineSize = 50;
//创建输出图像,图像大小根据输入源来确定
const int imagesHeight = nShowImageSize *
nSizeWindows.width + nAroundLineSize +
(nSizeWindows.width - 1) * nSplitLineSize;
const int imagesWidth = nShowImageSize *
nSizeWindows.height + nAroundLineSize +
(nSizeWindows.height - 1) * nSplitLineSize;
cout << imagesWidth << " " << imagesHeight << endl;
Mat showWindowsImages(imagesWidth, imagesHeight, CV_8UC3, Scalar(0, 0,
0));
//提取对应小图像的左上角坐标 x、y
int posX = (showWindowsImages.cols - (nShowImageSize * nSizeWindows.width +
(nSizeWindows.width - 1) * nSplitLineSize)) / 2;
int posY = (showWindowsImages.rows - (nShowImageSize * nSizeWindows.height +
(nSizeWindows.height - 1) * nSplitLineSize)) / 2;
cout << posX << " " << posY << endl;
int tempPosX = posX;
int tempPosY = posY;
//将每一幅小图像整合成一幅大图像
for (int i = 0; i < nNumImages; i++)
{
//小图像坐标转换
if ((i % nSizeWindows.width == 0) && (tempPosX != posX)) {
tempPosX = posX;;
tempPosY += (nSplitLineSize + nShowImageSize);
}
//利用 Rect 区域将小图像置于大图像的相应区域
Mat tempImage = showWindowsImages
(Rect(tempPosX, tempPosY, nShowImageSize, nShowImageSize));
//利用 resize 函数实现图像缩放
resize(srcImages[i], tempImage,
Size(nShowImageSize, nShowImageSize));
tempPosX += (nSplitLineSize + nShowImageSize);
}
imshow("showImages", showWindowsImages);
}
int main()
{
//图像源输入
vector<Mat>srcImage(9);
srcImage[0] = imread("test.jpg");
srcImage[1] = imread("test.jpg");
srcImage[2] = imread("test.jpg");
srcImage[3] = imread("test.jpg");
srcImage[4] = imread("test.jpg");
srcImage[5] = imread("test.jpg");
srcImage[6] = imread("test.jpg");
srcImage[7] = imread("test.jpg");
srcImage[8] = imread("test.jpg");
//判断当前 vector 读入的正确性
for (int i = 0; i < srcImage.size(); i++)
{
if (srcImage[i].empty())
{
cout << "read error" << endl;
return -1;
}
}
//调用单窗口显示图像
showManyImages(srcImage, Size(147, 118));
waitKey(0);
system("pause");
return 0;
}

3. 销毁窗口
既然有新建窗口,那么也有销毁窗口。在 OpenCV 中,销毁窗口时窗口将自动关闭,可以通过函数 destroyWindow 和 destroyAllWindows 来实现,前者是销毁某一个指定名称的窗口,后者是销毁所有新建的窗口。其中函数 destroyWindow 声明如下:
cpp
void destroyWindow(const String& winname);
其中参数 winname 是要销毁窗口的名称。
destroyWindow 函数更加简单,声明如下:
cpp
void destroyAllWindows();
下面试着来新建三个窗口,每个窗口显示 5 秒,再分别销毁。
cpp
#include <opencv2/highgui/highgui_c.h>
#include <opencv2\opencv.hpp>
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库
using namespace cv;
#include<iostream>
using namespace std;
int main()
{
//图像源输入
vector<Mat>srcImage(3);
char szName[50] = "";
for (int i = 0; i < srcImage.size(); i++)
{
sprintf_s(szName, "%d.jpg", i + 1);
srcImage[i] = imread(szName); //读取图像文件
if (srcImage[i].empty()) { //判断当前 vector 读入的正确性
cout << "read " << szName << " error" << endl;
return -1;
}
//调用单窗口显示图像
namedWindow(szName, WINDOW_AUTOSIZE);
imshow(szName, srcImage[i]);//在"窗口 1"这个窗口输出图像
waitKey(5000);//等待 5 秒,程序自动退出。改为 0,不自动退出
destroyWindow(szName);
}
//destroyAllWindows();
//cout << "所有的窗口已经销毁了" << endl;
waitKey(0);
system("pause");
return 0;
}



4.调整窗口大小
窗口大小可以手工拖拉窗口边框来调整,也可以通过函数方式来调整。调整窗口大小的函数是 resizeWindow,声明如下:
cpp
void resizeWindow(const String& winname, int width, int height);
其中参数 winname 是要调整尺寸的窗口的标题,width 是调整后的窗口宽度,height 是调整后的窗口高度。但要注意的是,新建窗口函数 namedWindow 的第二个参数必须为WINDOW_NORMAL 才可以手动拉动窗口边框来调整大小,虽然此时 resizeWindow 依旧可以调整窗口,但是图片不会随着窗口大小而改变。
cpp
#include <opencv2/highgui/highgui_c.h>
#include <opencv2\opencv.hpp>
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库
using namespace cv;
#include<iostream>
using namespace std;
int main()
{
vector<Mat>srcImage(1);
char szName[50] = "";
sprintf_s(szName, "%d.jpg", 1);
srcImage[0] = imread(szName);
if (srcImage[0].empty())
{ //判断当前 vector 读入的正确性
cout << "read " << szName << " error" << endl;
return -1;
}
namedWindow(szName, WINDOW_NORMAL); //新建窗口
imshow(szName, srcImage[0]); //在窗口显示图片
waitKey(2000);
int width = 640, height = 320;
resizeWindow(szName, width, height); //调整窗口大小
system("pause");
return 0;
}