《OpenCV3编程入门 》学习笔记 第3章 HighGUI图形用户界面初步

本章介绍opencv对图像的载入、显示和输出到文件进行详细地分析,讲解OpenCV中滑动条的创建和使用,以及如何用鼠标进行交互操作。

导读

在OpenCV架构分析一节中我们讲过,HighGUI模块为高层GUI图形用户界面模块,包含媒体的输入输出、视频捕捉、图像和视频的编码解码、图形交互界面的接口等内容。而在1.5节中讲到的VideoCapture视频类,就是出自此HighGUI模块。本章旨在为大家展开讲解OpenCV中最常用到的一些交互操作,包括图像的载入、显示和输出,为程序添加滑动条,以及鼠标操作等常用内容。

本章中,你将学到:

● 图像的载入、显示和输出到文件的详细分析

● 滑动条(Trackbar)的创建和使用

● OpenCV中的鼠标操作

3.1 图像的载入、显示和输出到文件

学习过以往版本OpenCV的读者应该都清楚,对于OpenCV1.0时代的基于C语言接口而建的图像存储格式IplImage*,如果在退出前忘记release掉的话,会造成内存泄露,而且用起来十分繁琐。我们在debug程序的时候,往往很大一部分时间会去纠结手动释放内存相关的问题。虽然对于小型的程序来说,手动管理内存不是什么难题,但一旦开发的项目日益庞大,代码量达到一定的规模,我们便会开始越来越多地纠缠于内存管理的问题,而不能把全部精力用于解决核心开发目标。因为不合适的图像存储数据结构而疲于维护日益庞大的项目,就有些舍本逐末的感觉了。

自踏入2.0版本的时代以来,OpenCV采用了Mat类作为数据结构进行图像存取。这一改进使OpenCV变得和几乎零门槛入门的Matlab一样,很容易上手和用于实际开发。新版OpenCV中甚至有些函数名称都和Matlab中的一样,比如大家所熟知的imread、imwrite、imshow等函数。这对于广大图像处理和计算机视觉领域的研究者们来说,的确是一件可喜可贺的事情。而这一小节中,我们主要来详细讲解OpenCV2、OpenCV3入门最基本的问题,即图像的载入、显示和输出。

3.1.1 OpenCV的命名空间

OpenCV中的C++类和函数都是定义在命名空间cv之内的,有两种方法可以访问:第一种,是在代码开头的适当位置加上usingnamespace cv;这句代码,规定程序位于此命名空间之内;另外一种,是在使用OpenCV的每一个类和函数时,都加入cv::命名空间。不过这种情况会很繁琐,每用一个OpenCV的类或者函数,都要多敲四下键盘写出cv::。所以,推荐大家在代码开头的适当位置,加上using namespace cv;这句。

比如在写简单的OpenCV程序的时候,以下三句可以作为标配:

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;

3.1.2 Mat类简析

Mat类是用于保存图像以及其他矩阵数据的数据结构,默认情况下其尺寸为0。我们也可以指定其初始尺寸,比如定义一个Mat类对象,就要写cv::Mat pic(320,640,cv::Scalar(100));

Mat类型作为OpenCV2、OpenCV3新纪元的重要代表,在稍后的章节中,笔者会花长篇幅详细讲解它,现在我们只要理解它是对应于OpenCV1.0时代的IplImage,主要用来存放图像的数据结构就行了。对于本节,我们需要用到关于Mat的其实就简单的这样一句代码:

Mat image = imread("dota.jpg");

这表示从工程目录下把一幅名为dota.jpg的jpg类型的图像载入到Mat类型的srcImage变量中。对于这里的imread函数,用于将图片读入Mat类型中,会在下文进行详细剖析。而Mat类,也会在第4章中进行更加全面的讲解。

3.1.3 图像的载入与显示概述

在新版本的OpenCV2中,最简单的图像载入和显示只需要两句代码,非常便捷。这两句代码分别对应了两个函数,它们分别是imread()以及imshow()。

3.1.4 图像的载入:imread()函数

首先来看imread函数,其用于读取文件中的图片到OpenCV中。可以在OpenCV官方文档中查到它的原型,如下。

Mat imread( const String& filename, int flags = IMREAD_COLOR );

(1)第一个参数,const string&类型的filename,填我们需要载入的图片路径名。在Windows操作系统下,OpenCV的imread函数支持如下类型的图像载入。

● Windows位图:*.bmp,*.dib

● JPEG文件:*.jpeg,*.jpg,*.jpe

● JPEG 2000文件:*.jp2

● PNG图片:*.png

● 便携文件格式:*.pbm,*.pgm,*.ppm

● Sun rasters光栅文件:*.sr,*.ras

● TIFF文件:*.tiff,*.tif

(2)第二个参数,int类型的flags,为载入标识,它指定一个加载图像的颜色类型。可以看到它自带默认值1,所以有时候这个参数在调用时可以忽略。在看了下面的讲解之后,我们就会发现,如果在调用时忽略这个参数,就表示载入三通道的彩色图像。这个参数可以在OpenCV中标识图像格式的枚举体中取值。通过转到定义,我们可以在imgcodecs.hpp中发现这个枚举的定义是这样的:

//! Imread flags
enum ImreadModes {
       IMREAD_UNCHANGED            = -1, //!< If set, return the loaded image as is (with alpha channel, otherwise it gets cropped). Ignore EXIF orientation.
       IMREAD_GRAYSCALE            = 0,  //!< If set, always convert image to the single channel grayscale image (codec internal conversion).
       IMREAD_COLOR                = 1,  //!< If set, always convert image to the 3 channel BGR color image.
       IMREAD_ANYDEPTH             = 2,  //!< If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.
       IMREAD_ANYCOLOR             = 4,  //!< If set, the image is read in any possible color format.
       IMREAD_LOAD_GDAL            = 8,  //!< If set, use the gdal driver for loading the image.
       IMREAD_REDUCED_GRAYSCALE_2  = 16, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/2.
       IMREAD_REDUCED_COLOR_2      = 17, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/2.
       IMREAD_REDUCED_GRAYSCALE_4  = 32, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/4.
       IMREAD_REDUCED_COLOR_4      = 33, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/4.
       IMREAD_REDUCED_GRAYSCALE_8  = 64, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/8.
       IMREAD_REDUCED_COLOR_8      = 65, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/8.
       IMREAD_IGNORE_ORIENTATION   = 128 //!< If set, do not rotate the image according to EXIF's orientation flag.
     };

因为flags是int型的变量,若我们不在这个枚举体中取固定的值,可以这样进行:
● flags>0返回一个3通道的彩色图像;
● flags=0返回灰度图像;
●flags<0返回包含Alpha通道的加载图像。
输出的图像默认情况下不返回Alpha通道。如果想要载入Alpha通道,这里就需要取负值。比如,将flags取1999也是可以的,和取1的效果一样,它们同样表示返回一个3通道的彩色图像。
另外,需要额外注意,若以彩色模式载入图像,解码后的图像会以BGR的通道顺序进行存储,即蓝、绿、红的顺序,而不是通常的RGB的顺序。
经过上面详细的讲解,我们一起看几个载入示例,以便多方位地掌握imread函数的用法。

Mat image0 = imread("1.jpg", 2 | 4);//载入无损的源图像
Mat image1 = imread("1.jpg", 0);//载八灰度图
Mat image2 = imread("1.jpg", 199);//载入3通道的彩色图像

3.1.5 图像的显示:imshow()函数

imshow()函数用于在指定的窗口中显示一幅图像,函数原型如下。

void imshow(const String& winname, InputArray mat);

● 第一个参数:const string&类型的winname,填需要显示的窗口标识名称。

● 第二个参数:InputArray类型的mat,填需要显示的图像。

imshow函数用于在指定的窗口中显示图像。如果窗口是用CV_WINDOW_AUTOSIZE(默认值)标志创建的,那么显示图像原始大小。否则,将图像进行缩放以适合窗口。而imshow函数缩放图像,取决于图像的深度,具体如下。

● 如果载入的图像是8位无符号类型(8-bit unsigned),就显示图像本来的样子。

● 如果图像是16位无符号类型(16-bit unsigned)或32位整型(32-bit integer),便用像素值除以256。也就是说,值的范围是[0,255 x 256]映射到[0,255]。

● 如果图像是32位浮点型(32-bit floating-point),像素值便要乘以255。也就是说,该值的范围是[0,1]映射到[0,255]。

还有一点,在窗口创建的时候,如果设定了支持OpenGL(WINDOW_OPENGL),那么imshow还支持ogl::Buffer、ogl::Texture2D以及gpu::GpuMat作为输入。

关于imwrite和imshow函数最精简的示例程序,可以参考1.3.8节“最终的测试”或1.4.1节“第一个程序:图像显示”中的代码。

3.1.6 关于InputArray类型

对于这里的InputArray类型,通过对InputArray转到定义,我们可以在mat.hpp中查到一个typedef声明,如下:

typedef const _InputArray& InputArray;

这其实一个类型声明引用,就是说_InputArray和InputArray是一个意思,因此我们就来做最后一步:对_InputArray进行转到定义,可以在core.hpp头文件中发现InputArray的真身。InputArray的源代码略显冗长,不在这里贴出,若读者自己去实践并通过转到定义的方法找到InputArray的真身,便可以看到_InputArray类的里面首先定义了一个枚举,然后是各类的模板类型和一些方法。而很多时候,遇到函数原型中的InputArray/OutputArray类型,我们把它简单地当做Mat类型即可。

3.1.7 创建窗口:namedWindow()函数

namedWindow函数用于创建一个窗口。若是简单地进行图片显示,可以略去namedWindow函数的调用,即先调用imread读入图片,然后用imshow直接指定出窗口名进行显示即可。但需要在显示窗口之前就用到窗口名时,比如我们后面会马上讲到滑动条的使用,要指定滑动条依附到某个窗口上,就需要namedWindow函数先创建出窗口,显式地规定窗口名称了。

namedWindow的函数原型如下:

void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);

(1)第一个参数,const string&型的name,填写被用作窗口的标识符的窗口名称。

(2)第二个参数,int类型的flags,窗口的标识,可以填如下几种值。

● WINDOW_NORMAL,设置这个值,用户可以改变窗口的大小(没有限制)。OpenCV2中它还可以写为CV_WINDOW_NORMAL。

● WINDOW_AUTOSIZE,设置这个值,窗口大小会自动调整以适应所显示的图像,并且用户不能手动改变窗口大小。OpenCV2中它还可以写为CV_WINDOW_AUTOSIZE。

● WINDOW_OPENGL,设置这个值,窗口创建的时候会支持OpenGL。OpenCV2中它还可以写为CV_WINDOW_OPENGL。

首先需要注意的是,namedWindow函数有默认值WINDOW_AUTOSIZE,所以,一般情况下,这个函数我们填一个变量就行了。namedWindow函数的作用是通过指定的名字,创建一个可以作为图像和进度条的容器窗口。如果具有相同名称的窗口已经存在,则函数不做任何事情。我们可以调用destroyWindow()或者destroyAllWindows()函数来关闭窗口,并取消之前分配的与窗口相关的所有内存空间。

但是事实上,对于代码量不大的简单程序来说,我们完全没有必要手动调用上述的destroyWindow()或者destroyAllWindows()函数,因为在退出时,所有的资源和应用程序的窗口会被操作系统自动关闭。

3.1.8 输出图像到文件:imwrite()函数

在OpenCV中,输出图像到文件一般采用imwrite函数,它的声明如下。

bool imwrite( const String& filename, InputArray img,  const std::vector<int>& params = std::vector<int>());

(1)第一个参数,const string&类型的filename,填需要写入的文件名。注意要带上后缀,如“123.jpg”。

(2)第二个参数,InputArray类型的img,一般填一个Mat类型的图像数据。

(3)第三个参数,const vector<int>&类型的params,表示为特定格式保存的参数编码。它有默认值vector<int>(),所以一般情况下不需要填写。而如果要填写的话,有下面这些需要了解的地方:

● 对于JPEG格式的图片,这个参数表示从0到100的图片质量(CV_IMWRITE_JPEG_QUALITY),默认值是95。

● 对于PNG格式的图片,这个参数表示压缩级别(CV_IMWRITE_PNG_COMPRESSION)从0到9。较高的值意味着更小的尺寸和更长的压缩时间,默认值是3。

● 对于PPM,PGM,或PBM格式的图片,这个参数表示一个二进制格式标志(CV_IMWRITE_PXM_BINARY),取值为0或1,默认值是1。

imwrite函数用于将图像保存到指定的文件。图像格式是基于文件扩展名的,可保存的扩展名和imread中可以读取的图像扩展名一致。

下面是一个示例程序,讲解imwrite函数的用法——在OpenCV中生成一幅png图片,并写入到当前工程目录下。


/*	@File				: 15_Use_imwrite.cpp
 *  @Brief				: 示例程序15
 *  @Details			: 利用imwrite函数生成带透明通道的png图像
 *  @Date				: 2015-11-01
 *  @OpenCV Version		: 4.8.0
 *  @Development Tools	: Windows 11 64bit && Visual Studio 2017
 *  @Modify				: 2024-03-31
*/
 
#include <vector>
#include <stdio.h>
#include<opencv2/opencv.hpp>

using namespace cv;
using namespace std;


//--------------------------------【createAlphaMat( )函数】--------------------------------
//		描述:创建带alpha通道的Mat
//-------------------------------------------------------------------------------------------------
void createAlphaMat(Mat &mat)
{
	for(int i = 0; i < mat.rows; ++i) {
		for(int j = 0; j < mat.cols; ++j) {
			Vec4b&rgba = mat.at<Vec4b>(i, j);
			rgba[0]= UCHAR_MAX;
			rgba[1]= saturate_cast<uchar>((float (mat.cols - j)) / ((float)mat.cols) *UCHAR_MAX);
			rgba[2]= saturate_cast<uchar>((float (mat.rows - i)) / ((float)mat.rows) *UCHAR_MAX);
			rgba[3]= saturate_cast<uchar>(0.5 * (rgba[1] + rgba[2]));
		}
	}
}



//-----------------------------------【ShowHelpText( )函数】----------------------------------
//          描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	printf("\n\n\t\t\t此为本书OpenCV3版的第15个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
	printf("\n\n  ----------------------------------------------------------------------------\n");
}



int main( )
{
	//创建带alpha通道的Mat
	Mat mat(480, 640, CV_8UC4);
	createAlphaMat(mat);

	ShowHelpText();

	vector<int>compression_params;
	//此句代码的OpenCV2版为:
	//compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
	//此句代码的OpenCV3版为:
	compression_params.push_back(IMWRITE_PNG_COMPRESSION);
	compression_params.push_back(9);

	//显示图片
	try{
		imwrite("透明Alpha值图.png", mat, compression_params);
		imshow("生成的png图",mat);
		fprintf(stdout,"PNG图片文件的alpha数据保存完毕~\n可以在工程目录下查看由imwrite函数生成的图片\n");
		waitKey(0);
	}
	catch(runtime_error& ex) {
		fprintf(stderr,"图像转换成PNG格式发生错误:%s\n", ex.what());
		return 1;
	}

	return 0;
}

程序运行截图如图3.1所示。运行完毕,我们可以在工程目录下发现一张生成的名为“透明Alpha值图.png”的图片文件。 

                                                图3.1 由imwrite生成的图 

3.1.9 综合示例程序:图像的载入、显示与输出

本小节将给出一个综合示例,演示如何载入图像,进行简单的图像混合,显示图像,并且输出混合后的图像到jpg格式的文件中。

出于注重演示效果的原因,程序中图像混合的具体细节我们放到稍后的篇幅中再讲,现在先给大家看看混合的效果和源码。以下就是本节的综合示例程序,经过详细注释的代码非常简单明了。


/*	@File				: 16_LoadShowOutputImage.cpp
 *  @Brief				: 示例程序16
 *  @Details			: 图像的载入、显示和输出示例
 *  @Date				: 2015-11-01
 *  @OpenCV Version		: 4.8.0
 *  @Development Tools	: Windows 11 64bit && Visual Studio 2017
 *  @Modify				: 2024-03-31
*/


#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;

int main( )
{
	//-----------------------------------【一、图像的载入和显示】---------------------------------
	//	描述:以下三行代码用于完成图像的载入和显示
	//--------------------------------------------------------------------------------------------------

	Mat girl=imread("girl.jpg");	//载入图像到Mat
	namedWindow("【1】动漫图");		//创建一个名为 "【1】动漫图"的窗口  
	imshow("【1】动漫图",girl);		//显示名为 "【1】动漫图"的窗口  

	//-----------------------------------【二、初级图像混合】--------------------------------------
	//	描述:二、初级图像混合
	//--------------------------------------------------------------------------------------------------
	//载入图片
	//Mat image= imread("dota.jpg",199); //这里不能写199,否则会报错
	Mat image = imread("dota.jpg");
	Mat logo= imread("dota_logo.jpg"); //200 X 200

	//载入后先显示
	namedWindow("【2】原画图");
	imshow("【2】原画图",image);

	namedWindow("【3】logo图");
	imshow("【3】logo图",logo);

	// 定义一个Mat类型,用于存放,图像的ROI
	Mat imageROI;
	//方法一
	imageROI= image(Rect(800,350,logo.cols,logo.rows));
	//方法二
	//imageROI= image(Range(350,350+logo.rows),Range(800,800+logo.cols));

	// 将logo加到原图上
	addWeighted(imageROI, 0.5, logo, 0.3, 0., imageROI);

	//显示结果
	namedWindow("【4】原画+logo图");
	imshow("【4】原画+logo图",image);

	//-----------------------------------【三、图像的输出】--------------------------------------
	//	描述:将一个Mat图像输出到图像文件
	//-----------------------------------------------------------------------------------------------
	//输出一张jpg图片到工程目录下
	imwrite("由imwrite生成的图片.jpg",image);

	waitKey();

	return 0;
}

运行这个程序,会弹出4个在OpenCV中创建的窗口。下面是运行截图。首先是图像载入和显示的演示,我们载入了一张动漫人物图,如图3.2所示。

                                    图3.2 动漫显示图

接着是载入一张dota2原画(图3.3)和dota2的logo(图3.4),为图像融合做准备。

                                                   图3.3 dota2原画图窗口

         图3.4 logo图

最终,经过处理,得到dota2原画+logo的融合,并输出一张名为“由imwrite生成的图片.jpg”的图片到工程目录下。如图3.5所示。

                                                   图3.5 融合后的效果图

3.2 滑动条的创建和使用

滑动条(Trackbar)是OpenCV动态调节参数特别好用的一种工具,它依附于窗口而存在。

由于OpenCV中并没有实现按钮的功能,所以很多时候,我们还可以用仅含0-1的滑动条来实现按钮的按下、弹起效果。

3.2.1 创建滑动条:createTrackbar()函数

createTrackbar函数用于创建一个可以调整数值的滑动条(常常也被称作轨迹条),并将滑动条附加到指定的窗口上,使用起来很方便。需要记住,它往往会和一个回调函数配合起来使用。先看下它的函数原型,如下。

int createTrackbar(const String& trackbarname, const String& winname,
                              int* value, int count,
                              TrackbarCallback onChange = 0,
                              void* userdata = 0);

● 第一个参数,const string&类型的trackbarname,轨迹条的名字,用来代表我们创建的轨迹条。

● 第二个参数,const string&类型的winname,窗口的名字,表示这个轨迹条会依附到哪个窗口上,即对应namedWindow()创建窗口时填的某一个窗口名。

● 第三个参数,int*类型的value,一个指向整型的指针,表示滑块的位置。在创建时,滑块的初始位置就是该变量当前的值。

● 第四个参数,int类型的count,表示滑块可以达到的最大位置的值。滑块最小位置的值始终为0。

● 第五个参数,TrackbarCallback类型的onChange,它有默认值0。这是一个指向回调函数的指针,每次滑块位置改变时,这个函数都会进行回调。并且这个函数的原型必须为void XXXX(int, void*);,其中第一个参数是轨迹条的位置,第二个参数是用户数据(看下面的第六个参数)。如果回调是NULL指针,则表示没有回调函数的调用,仅第三个参数value有变化。

● 第六个参数,void*类型的userdata,也有默认值0。这个参数是用户传给回调函数的数据,用来处理轨迹条事件。如果使用的第三个参数value实参是全局变量的话,完全可以不去管这个userdata参数。

createTrackbar函数为我们创建了一个具有特定名称和范围的轨迹条(Trackbar,或者说是滑块范围控制工具),指定一个和轨迹条位置同步的变量,而且要指定回调函数onChange(第五个参数),在轨迹条位置改变的时候来调用这个回调函数,并且,创建的轨迹条显示在指定的winname(第二个参数)所代表的窗口上。

至于回调函数,就是一个通过函数指针调用的函数。如果我们把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就称其为回调函数。回调函数不由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用,用于对该事件或条件进行响应。

在函数讲解之后,给大家一个createTrackbar函数使用的小例子作为参照。

接着,我们一起来欣赏一个完整的使用示例,它演示了如何用轨迹条来控制两幅图像的Alpha混合。


/*	@File				: 17_CreateTrackbar.cpp
 *  @Brief				: 示例程序17
 *  @Details			: 为程序界面添加滑动条
 *  @Date				: 2015-11-01
 *  @OpenCV Version		: 4.8.0
 *  @Development Tools	: Windows 11 64bit && Visual Studio 2017
 *  @Modify				: 2024-03-31
*/

#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>

#include <stdio.h>

using namespace cv;

//-----------------------------------【宏定义部分】-------------------------------------------- 
//  描述:定义一些辅助宏 
//------------------------------------------------------------------------------------------------ 
#define WINDOW_NAME "【滑动条的创建&线性混合示例】"        //为窗口标题定义的宏 


//-----------------------------------【全局变量声明部分】--------------------------------------
//		描述:全局变量声明
//-----------------------------------------------------------------------------------------------
const int g_nMaxAlphaValue = 100;//Alpha值的最大值
int g_nAlphaValueSlider;//滑动条对应的变量
double g_dAlphaValue;
double g_dBetaValue;

//声明存储图像的变量
Mat g_srcImage1;
Mat g_srcImage2;
Mat g_dstImage;


//-----------------------------------【on_Trackbar( )函数】--------------------------------
//		描述:响应滑动条的回调函数
//------------------------------------------------------------------------------------------
void on_Trackbar( int, void* )
{
	//求出当前alpha值相对于最大值的比例
	g_dAlphaValue = (double) g_nAlphaValueSlider/g_nMaxAlphaValue ;
	//则beta值为1减去alpha值
	g_dBetaValue = ( 1.0 - g_dAlphaValue );

	//根据alpha和beta值进行线性混合
	addWeighted( g_srcImage1, g_dAlphaValue, g_srcImage2, g_dBetaValue, 0.0, g_dstImage);

	//显示效果图
	imshow( WINDOW_NAME, g_dstImage );
}


//-----------------------------【ShowHelpText( )函数】--------------------------------------
//		描述:输出帮助信息
//-------------------------------------------------------------------------------------------------
//-----------------------------------【ShowHelpText( )函数】----------------------------------
//		描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	printf("\n\n\t\t\t此为本书OpenCV3版的第17个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
	printf("\n\n  ----------------------------------------------------------------------------\n");
}


//--------------------------------------【main( )函数】-----------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{

	//显示帮助信息
	ShowHelpText();

	//加载图像 (两图像的尺寸需相同)
	g_srcImage1 = imread("1.jpg");
	g_srcImage2 = imread("2.jpg");
	if( !g_srcImage1.data ) { printf("读取第一幅图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return -1; }
	if( !g_srcImage2.data ) { printf("读取第二幅图片错误,请确定目录下是否有imread函数指定图片存在~!\n"); return -1; }

	//设置滑动条初值为70
	g_nAlphaValueSlider = 70;

	//创建窗体
	namedWindow(WINDOW_NAME, 1);

	//在创建的窗体中创建一个滑动条控件
	char TrackbarName[50];
	sprintf_s( TrackbarName, "透明值 %d", g_nMaxAlphaValue );

	createTrackbar( TrackbarName, WINDOW_NAME, &g_nAlphaValueSlider, g_nMaxAlphaValue, on_Trackbar );

	//结果在回调函数中显示
	on_Trackbar( g_nAlphaValueSlider, 0 );

	//按任意键退出
	waitKey(0);

	return 0;
}

运行此程序,我们可以通过调节滑动条的位置,来得到不同的混合效果。如图3.6、图3.7、图3.8所示。

                  图3.6 透明值为70时的效果图

                     图3.7 透明值为100时的效果图

                  图3.8 透明值为0时的效果图

3.2.2 获取当前轨迹条的位置:getTrackbarPos()函数

本小节介绍一个配合createTrackbar使用的函数——getTrackbarPos(),它用于获取当前轨迹条的位置。

下面这个函数用于获取当前轨迹条的位置并返回。

int getTrackbarPos(const String& trackbarname, const String& winname);

● 第一个参数,const string&类型的trackbarname,表示轨迹条的名字。

● 第二个参数,const string&类型的winname,表示轨迹条的父窗口的名称。

3.3 鼠标操作

OpenCV中的鼠标操作和滑动条的消息映射方式很类似,都是通过一个中介函数配合一个回调函数来实现的。创建和指定滑动条回调函数的函数为createTrackbar,而指定鼠标操作消息回调函数的函数为SetMouseCallback。下面一起来了解一下它。

void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);

● 第一个参数,const string&类型的winname,窗口的名字。

● 第二个参数,MouseCallback类型的onMouse,指定窗口里每次鼠标时间发生的时候,被调用的函数指针。这个函数的原型的大概形式为void Foo(int event,int x, int y, int flags, void*param)。其中event是EVENT_+变量之一,x和y是鼠标指针在图像坐标系(需要注意,不是窗口坐标系)中的坐标值,flags是EVENT_FLAG的组合,param是用户定义的传递到SetMouseCallback函数调用的参数。如EVENT_MOUSEMOVE为鼠标移动消息、EVENT_LBUTTONDOWN为鼠标左键按下消息等。

在OpenCV2中,上述“EVENT_”之前可以加上“CV_”前缀。

● 第三个参数,void*类型的userdata,用户定义的传递到回调函数的参数,有默认值0。

下面看一个详细注释的示例程序,在实战中了解此函数的用法以及如何在OpenCV中使用鼠标进行交互。


/*	@File				: 18_UseMouse.cpp
 *  @Brief				: 示例程序18
 *  @Details			: HelloOpenCV
 *  @Date				: 2015-11-01
 *  @OpenCV Version		: 4.8.0
 *  @Development Tools	: Windows 11 64bit && Visual Studio 2017
 *  @Modify				: 2024-03-31
*/


#include <opencv2/opencv.hpp>
using namespace cv;

//-----------------------------------【宏定义部分】--------------------------------------------
//  描述:定义一些辅助宏 
//------------------------------------------------------------------------------------------------ 
#define WINDOW_NAME "【程序窗口】"        //为窗口标题定义的宏 


//-----------------------------------【全局函数声明部分】------------------------------------
//		描述:全局函数的声明
//------------------------------------------------------------------------------------------------
void on_MouseHandle(int event, int x, int y, int flags, void* param);
void DrawRectangle( cv::Mat& img, cv::Rect box );
void ShowHelpText( );

//-----------------------------------【全局变量声明部分】-----------------------------------
//		描述:全局变量的声明
//-----------------------------------------------------------------------------------------------
Rect g_rectangle;
bool g_bDrawingBox = false;//是否进行绘制
RNG g_rng(12345);



//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-------------------------------------------------------------------------------------------------
int main( int argc, char** argv ) 
{
	//【0】改变console字体颜色
	system("color 9F"); 

	//【0】显示欢迎和帮助文字
	ShowHelpText();

	//【1】准备参数
	g_rectangle = Rect(-1,-1,0,0);
	Mat srcImage(600, 800,CV_8UC3), tempImage;
	srcImage.copyTo(tempImage);
	g_rectangle = Rect(-1,-1,0,0);
	srcImage = Scalar::all(0);

	//【2】设置鼠标操作回调函数
	namedWindow( WINDOW_NAME );
	setMouseCallback(WINDOW_NAME,on_MouseHandle,(void*)&srcImage);

	//【3】程序主循环,当进行绘制的标识符为真时,进行绘制
	while(1)
	{
		srcImage.copyTo(tempImage);//拷贝源图到临时变量
		if( g_bDrawingBox ) DrawRectangle( tempImage, g_rectangle );//当进行绘制的标识符为真,则进行绘制
		imshow( WINDOW_NAME, tempImage );
		if( waitKey( 10 ) == 27 ) break;//按下ESC键,程序退出
	}
	return 0;
}



//--------------------------------【on_MouseHandle( )函数】-----------------------------
//		描述:鼠标回调函数,根据不同的鼠标事件进行不同的操作
//-----------------------------------------------------------------------------------------------
void on_MouseHandle(int event, int x, int y, int flags, void* param)
{

	Mat& image = *(cv::Mat*) param;
	switch( event)
	{
		//鼠标移动消息
	case EVENT_MOUSEMOVE: 
		{
			if( g_bDrawingBox )//如果是否进行绘制的标识符为真,则记录下长和宽到RECT型变量中
			{
				g_rectangle.width = x-g_rectangle.x;
				g_rectangle.height = y-g_rectangle.y;
			}
		}
		break;

		//左键按下消息
	case EVENT_LBUTTONDOWN: 
		{
			g_bDrawingBox = true;
			g_rectangle =Rect( x, y, 0, 0 );//记录起始点
		}
		break;

		//左键抬起消息
	case EVENT_LBUTTONUP: 
		{
			g_bDrawingBox = false;//置标识符为false
			//对宽和高小于0的处理
			if( g_rectangle.width < 0 )
			{
				g_rectangle.x += g_rectangle.width;
				g_rectangle.width *= -1;
			}

			if( g_rectangle.height < 0 ) 
			{
				g_rectangle.y += g_rectangle.height;
				g_rectangle.height *= -1;
			}
			//调用函数进行绘制
			DrawRectangle( image, g_rectangle );
		}
		break;

	}
}



//-----------------------------------【DrawRectangle( )函数】------------------------------
//		描述:自定义的矩形绘制函数
//-----------------------------------------------------------------------------------------------
void DrawRectangle( cv::Mat& img, cv::Rect box )
{
	cv::rectangle(img,box.tl(),box.br(),cv::Scalar(g_rng.uniform(0, 255), g_rng.uniform(0,255), g_rng.uniform(0,255)));//随机颜色
}


//-----------------------------------【ShowHelpText( )函数】-----------------------------
//          描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	printf("\n\n\t\t\t此为本书OpenCV3版的第18个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
	printf("\n\n  ----------------------------------------------------------------------------\n");
	//输出一些帮助信息
	printf("\n\n\n\t欢迎来到【鼠标交互演示】示例程序\n"); 
	printf("\n\n\t请在窗口中点击鼠标左键并拖动以绘制矩形\n");

}

首先一起看看程序运行效果。我们可以通过鼠标左键的按下和松开来在黑色的窗口中绘制出一个一个彩色的矩形。如图3.9、图3.10所示。

                                                         图3.9 运行截图(1)

                                                    图3.10 运行截图(2)

上述示例程序中的on_MouseHandle就是我们的鼠标消息回调函数。其中用一个switch语句指出了各种类型的鼠标消息,如鼠标移动消息EVENT_MOUSEMOVE、左键按下消息EVENT_LBUTTONDOWN、左键抬起消息EVENT_LBUTTONUP,并对其进行了处理,以得到随机颜色的矩形绘制的功能。

3.4 本章小结

本章中,我们学习了OpenCV的高层GUI图形用户界面模块highgui中最重要的几个方面,分别是图像的载入、显示与输出图像到文件,以及如何使用滑动条如何进行鼠标操作。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值