跳转至

三、学习图形用户界面

第 2 章OpenCV 基础知识中,我们学习了 OpenCV 的基本类和结构,以及最重要的类Mat。 我们学习了如何读取和保存图像和视频,以及图像记忆的内部结构。 我们现在已经准备好使用 OpenCV,但在大多数情况下,我们需要显示图像结果,并使用多个用户界面检索用户与图像的交互。 OpenCV 为我们提供了几个基本的用户界面,帮助我们创建应用和原型。为了更好地理解用户界面是如何工作的,我们将在本章末尾创建一个名为PhotoTool的小型应用。 在本应用中,我们将学习如何使用滤镜和颜色转换。

本章介绍以下主题:

  • OpenCV 基本用户界面
  • OpenCV Qt 界面
  • 滑块和按钮
  • 高级用户界面-OpenGL
  • 颜色转换
  • 基本过滤器

技术要求

本章要求熟悉基本的 C++ 编程语言。 本章使用的所有代码都可以从以下 gihub 链接下载:https://github.com/PacktPublishing/Building-Computer-Vision-Projects-with-OpenCV4-and-CPlusPlus/tree/master/Chapter03。这些代码可以在任何操作系统上执行,尽管它只在 Ubuntu 上测试过。

请查看以下视频以了解代码的实际使用情况: http://bit.ly/2KH2QXD

OpenCV 用户界面简介

OpenCV 拥有自己的跨操作系统用户界面,允许开发人员创建自己的应用,而无需学习复杂的用户界面库。 OpenCV 用户界面是基本的,但它为计算机视觉开发人员提供了创建和管理其软件开发的基本功能。 所有这些都是原生的,并针对实时使用进行了优化。

OpenCV 提供两个用户界面选项:

  • 基于原生用户界面的基本界面,适用于 Mac OS X 的 Cocoa 或 Carbon,以及适用于 Linux 或 Windows 用户界面的 GTK,在编译 OpenCV 时默认选择。
  • 一个基于 Qt 库的更高级的界面,它是一个跨平台的界面。 在编译 OpenCV 之前,您必须在 CMake 中手动启用 Qt 选项。

在下面的屏幕截图中,您可以看到左侧的基本用户界面窗口和右侧的 Qt 用户界面:

使用 OpenCV 的基本图形用户界面

我们将使用 OpenCV 创建一个基本的用户界面。 OpenCV 用户界面允许我们创建窗口,向其中添加图像,以及移动、调整大小和销毁窗口。 用户界面在 OpenCV 的highui模块中。在下面的代码中,我们将学习如何通过按一个键来显示多个窗口,同时图像在桌面的窗口中移动,从而创建和显示两个图像。

不要担心阅读完整的代码;我们将分成小块进行解释:

#include <iostream> 
#include <string> 
#include <sstream> 
using namespace std; 

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

int main(int argc, const char** argv) 
{ 
   // Read images 
   Mat lena= imread("../lena.jpg"); 
   # Checking if Lena image has been loaded
   if (!lena.data) {
 cout << "Lena image missing!" << enld;
 return -1;
   }
   Mat photo= imread("../photo.jpg"); 
   # Checking if Lena image has been loaded
   if (!photo.data) {
 cout << "Lena image missing!" << enld;
 return -1;
 }

   // Create windows 
   namedWindow("Lena", WINDOW_NORMAL); 
   namedWindow("Photo", WINDOW_AUTOSIZE); 

   // Move window 
   moveWindow("Lena", 10, 10); 
   moveWindow("Photo", 520, 10); 

   // show images 
   imshow("Lena", lena); 
   imshow("Photo", photo);  

   // Resize window, only non autosize 
   resizeWindow("Lena", 512, 512);  

   // wait for any key press 
   waitKey(0); 

   // Destroy the windows 
   destroyWindow("Lena"); 
   destroyWindow("Photo"); 

   // Create 10 windows 
   for(int i =0; i< 10; i++) 
   { 
         ostringstream ss; 
         ss << "Photo" << i; 
         namedWindow(ss.str()); 
         moveWindow(ss.str(), 20*i, 20*i); 
         imshow(ss.str(), photo); 
   } 

   waitKey(0); 
   // Destroy all windows 
   destroyAllWindows(); 
   return 0; 
} 

让我们来了解一下代码:

  1. 为了方便图形用户界面,我们必须执行的第一个任务是导入 OpenCV 的highui模块:
#include <opencv2/highgui.hpp> 
  1. 现在我们已经准备好创建我们的新窗口,我们必须加载一些图像:
// Read images 
Mat lena= imread("../lena.jpg"); 
Mat photo= imread("../photo.jpg"); 
  1. 要创建窗口,我们使用namedWindow函数。 该函数有两个参数;第一个参数是包含窗口名称的常量字符串,第二个参数是我们需要的标志。 第二个参数是可选的:
namedWindow("Lena", WINDOW_NORMAL); 
namedWindow("Photo", WINDOW_AUTOSIZE);
  1. 在我们的示例中,我们创建了两个窗口:第一个窗口称为Lena,第二个窗口称为Photo

默认情况下,Qt 和 NATIVE 有三个标志:

Qt 具有多个附加标志:

If we compile OpenCV with Qt, all the windows that we create are, by default, in the expanded interface, but we can use native interfaces and more basic ones adding the CV_GUI_NORMAL flag. By default, the flags are WINDOW_AUTOSIZEWINDOW_KEEPRATIO, and WINDOW_GUI_EXPANDED.

  1. 当我们创建多个窗口时,它们是重叠的,但我们可以使用moveWindow函数将窗口移动到桌面的任何区域,如下所示:
// Move window 
moveWindow("Lena", 10, 10); 
moveWindow("Photo", 520, 10); 
  1. 在我们的代码中,我们向左移动Lena窗口10像素,向上移动10像素,向左移动Photo窗口520像素,向上移动10像素:
// show images 
imshow("Lena", lena); 
imshow("Photo", photo);  
// Resize window, only non autosize 
resizeWindow("Lena", 512, 512);
  1. 在显示了我们之前使用imshow函数加载的图像之后,我们调用resizeWindow函数,将Lena窗口的大小调整为512像素。 该函数有三个参数:window namewidthheight

The specific window size is for the image area. Toolbars are not counted. Only windows without the WINDOW_AUTOSIZE flag enabled can be resized.

  1. 在使用waitKey函数等待按键后,我们将使用destroyWindow函数移除或删除我们的窗口,其中窗口名称是唯一必需的参数:
waitKey(0); 

// Destroy the windows 
destroyWindow("Lena"); 
destroyWindow("Photo"); 
  1. OpenCV 有一个功能,可以删除我们在一次调用中创建的所有窗口。 该函数称为destroyAllWindows。 为了演示其工作原理,我们在样例中创建了 10 个窗口,并等待按键。 当用户按任意键时,它会销毁所有窗口:
 // Create 10 windows 
for(int i =0; i< 10; i++) 
{ 
   ostringstream ss; 
   ss << "Photo" << i; 
   namedWindow(ss.str()); 
   moveWindow(ss.str(), 20*i, 20*i); 
   imshow(ss.str(), photo); 
} 

waitKey(0); 
// Destroy all windows 
destroyAllWindows(); 

在任何情况下,OpenCV 都会在应用终止时自动处理所有窗口的销毁,并且没有必要在应用结束时调用此函数。

所有这些代码的结果可以在下面两个步骤的图像中看到。 首先,它显示两个窗口:

按下任意键后,应用将继续并绘制几个改变位置的窗口:

只需几行代码,我们就能够创建和操作窗口并显示图像。 我们现在已经准备好促进用户与图像的交互,并添加用户界面控件。

将滑块和鼠标事件添加到我们的界面

鼠标事件和滑块控制在计算机视觉和 OpenCV 中非常有用。 使用这些控件用户,我们可以直接与界面交互,并更改输入图像或变量的属性。在本节中,我们将介绍用于基本交互的鼠标事件和滑块控件。 为了便于正确理解,我们创建了以下代码,通过这些代码,我们将使用鼠标事件在图像中绘制绿色圆圈,并使用鼠标滑块模糊图像:

// Create a variable to save the position value in track 
int blurAmount=15; 

// Trackbar call back function 
static void onChange(int pos, void* userInput); 

//Mouse callback 
static void onMouse(int event, int x, int y, int, void* userInput); 

int main(int argc, const char** argv) 
{ 
   // Read images 
   Mat lena= imread("../lena.jpg"); 

   // Create windows 
   namedWindow("Lena"); 

   // create a trackbar 
   createTrackbar("Lena", "Lena", &blurAmount, 30, onChange, &lena); 

   setMouseCallback("Lena", onMouse, &lena); 

   // Call to onChange to init 
   onChange(blurAmount, &lena); 

   // wait app for a key to exit 
   waitKey(0); 

   // Destroy the windows 
   destroyWindow("Lena"); 

   return 0; 
} 

让我们来理解一下代码!

首先,我们创建一个变量来保存滑块位置。 我们需要保存滑块位置,以便从其他功能访问:

// Create a variable to save the position value in track 
int blurAmount=15;

现在,我们定义滑块和鼠标事件的回调,这是 OpenCV 函数setMouseCallbackcreateTrackbar所需的:

// Trackbar call back function 
static void onChange(int pos, void* userInput); 

//Mouse callback 
static void onMouse(int event, int x, int y, int, void* userInput); 

在 main 函数中,我们加载一个图像并创建一个名为Lena的新窗口:

int main(int argc, const char** argv) 
{ 
   // Read images 
   Mat lena= imread("../lena.jpg"); 

   // Create windows 
   namedWindow("Lena"); 

现在是创建滑块的时候了。 OpenCV 具有createTrackbar功能,可按顺序生成具有以下参数的滑块:

  1. 轨迹栏名称。
  2. 窗口名称。
  3. 用作值的整数指针;此参数是可选的。 如果设置了该选项,则滑块在创建时将达到此位置。
  4. 滑块上的最大位置。
  5. 滑块位置更改时的回调函数。
  6. 要发送到回调的用户数据。 它可用于在不使用全局变量的情况下将数据发送到回调。

在此代码中,我们为Lena窗口添加了trackbar,并调用了Lena跟踪条,以便模糊图像。 跟踪条的值存储在我们作为指针传递的第一个blurAmount整数中,并将该条的最大值设置为30。 我们将onChange设置为回调函数,并将 Lena Mat 图像作为用户数据发送:

   // create a trackbar 
   createTrackbar("Lena", "Lena", &blurAmount, 30, onChange, &lena);

创建滑块后,当用户单击鼠标左键时,我们将鼠标事件添加到绘制圆圈中。 OpenCV 具有setMouseCallback测试功能。 此函数有三个参数:

  • 我们在其中获取鼠标事件的窗口名称。

  • 当有任何鼠标交互时要调用的回调函数。

  • 用户数据:这是调用回调函数时将发送给它的任何数据。 在我们的示例中,我们将发送整个Lena图像。

使用以下代码,我们可以将鼠标回调添加到Lena窗口,并将onMouse设置为回调函数,将 Lena Mat 图像作为用户数据进行传递:

setMouseCallback("Lena", onMouse, &lena); 

要只完成 Main 函数,我们需要使用与滑块相同的参数来初始化图像。 要执行初始化,我们只需调用参数onChange的回调函数,并在使用destroyWindow关闭窗口之前等待事件,如以下代码所示:

// Call to onChange to init   
onChange(blurAmount, &lena); 

// wait app for a key to exit 
waitKey(0); 

// Destroy the windows 
destroyWindow("Lena"); 

滑块回调使用滑块值作为模糊量对图像应用基本模糊滤镜:

// Trackbar call back function 
static void onChange(int pos, void* userData) { 
    if(pos <= 0) return; 
    // Aux variable for result 
    Mat imgBlur; 
    // Get the pointer input image     
    Mat* img= (Mat*)userInput; 
    // Apply a blur filter 
    blur(*img, imgBlur, Size(pos, pos)); 
    // Show the result 
    imshow("Lena", imgBlur); 
}

此函数使用变量pos检查滑块值是否为0。 在这种情况下,我们不应用筛选器,因为它会生成错误的执行。 我们也不能应用0像素模糊。 在检查滑块值之后,我们创建一个名为imgBlur的空矩阵来存储模糊结果。 要在回调函数中检索通过用户数据发送的图像,我们必须将void* userData值强制转换为正确的图像类型指针Mat*

现在我们有了正确的变量来应用模糊滤镜。 模糊函数将基本中值滤波器应用于输入图像,在我们的示例中为*img;对于输出图像,最后需要的参数是我们要应用的模糊核的大小(核是用于计算核与图像之间的卷积均值的小矩阵)。 在我们的例子中,我们使用的是大小为pos的平方核。 最后,我们只需要使用imshow函数更新图像界面。

鼠标事件回调有五个输入参数:第一个参数定义事件类型;第二个和第三个参数定义鼠标位置;第四个参数定义滚轮移动;第五个参数定义用户输入数据。

鼠标事件类型如下:

| 事件类型 | 描述 / 描写 / 形容 / 类别 | | EVENT_MOUSEMOVE | 当用户移动鼠标时。 | | EVENT_LBUTTONDOWN | 当用户单击鼠标左键时。 | | EVENT_RBUTTONDOWN | 当用户单击鼠标右键时。 | | EVENT_MBUTTONDOWN | 当用户单击鼠标中键时。 | | EVENT_LBUTTONUP | 当用户释放鼠标左键时。 | | EVENT_RBUTTONUP | 当用户释放鼠标右键时。 | | EVENT_MBUTTONUP | 当用户释放鼠标中键时。 | | EVENT_LBUTTONDBLCLK | 当用户双击鼠标左键时。 | | EVENT_RBUTTONDBLCLK | 当用户双击鼠标右键时。 | | EVENT_MBUTTONDBLCLK | 当用户双击鼠标中键时。 | | EVENTMOUSEWHEEL | 当用户使用鼠标滚轮执行垂直滚动时。 | | EVENT_MOUSEHWHEEL | 当用户使用鼠标滚轮执行水平滚动时。 |

在我们的示例中,我们只管理由鼠标左键单击产生的事件,并且丢弃除EVENT_LBUTTONDOWN之外的任何事件。 丢弃其他事件后,我们会通过滑块回调获得类似的输入图片,并使用 OpenCV 函数中的圆圈来获取图片中的圆圈:

//Mouse callback 
static void onMouse(int event, int x, int y, int, void* userInput) 
{ 
   if(event != EVENT_LBUTTONDOWN) 
           return; 

   // Get the pointer input image 
   Mat* img= (Mat*)userInput; 

   // Draw circle 
   circle(*img, Point(x, y), 10, Scalar(0,255,0), 3); 

   // Call on change to get blurred image 
   onChange(blurAmount, img); 

} 

使用 Qt 的图形用户界面

Qt 用户界面为我们提供了更多的控制和选项来处理我们的图像。

该界面分为以下三个主要区域:

  • 工具栏
  • 图像区域
  • 状态栏

我们可以在下图中看到这三个区域。 图片顶部是工具栏,图片是主区域,图片底部可以看到状态栏:

工具栏从左到右有以下按钮:

  • 四个平移按钮
  • 缩放 x1
  • 缩放 x30,显示标签
  • 放大
  • 拉远 / 拉远镜头
  • 保存当前图像
  • 显示属性

这些选项在下图中可以清楚地看到:

当我们在图像上按鼠标右键时,图像区域会显示一个图像和一个上下文菜单。 该区域可以使用displayOverlay函数在区域顶部显示覆盖消息。该函数接受三个参数:窗口名称、我们想要显示的文本以及覆盖文本显示的毫秒周期。 如果将此时间设置为0,则文本永远不会消失:

// Display Overlay 
displayOverlay("Lena", "Overlay 5secs", 5000);

我们可以在下图中看到前面代码的结果。 你可以在图片的顶部看到一个小黑框,上面有一句话叠加了 5 秒:

最后,状态栏显示窗口的底部,并显示图像中坐标的像素值和位置:

我们可以使用状态栏以覆盖的方式显示消息。 可以更改状态栏消息的函数是displayStatusBar。此函数与覆盖函数具有相同的参数:窗口名称、要显示的文本以及显示它的时间段:

向用户界面添加按钮

在前面的小节中,我们学习了如何创建普通或 Qt 界面,并使用鼠标和滑块与它们交互,但我们也可以创建不同类型的按钮。

Buttons are only supported in Qt windows.

OpenCV Qt 支持的按钮类型如下:

  • 按钮
  • 校验框
  • 无线电箱

这些按钮仅显示在控制面板中。 控制面板是每个程序的一个独立窗口,我们可以在其中附加按钮和轨迹条。要显示控制面板,我们可以按下最后一个工具栏按钮,右键单击 Qt 窗口的任何部分并选择显示属性窗口,或者使用Ctrl+P快捷键。让我们创建一个带有按钮的基本示例。 代码很丰富,我们将先解释 Main 函数,然后再分别解释每个回调函数,以便更好地理解所有内容。 下面的代码向我们展示了生成用户界面的主要代码函数:

Mat img; 
bool applyGray=false; 
bool applyBlur=false; 
bool applySobel=false; 
... 
int main(int argc, const char** argv) 
{ 
   // Read images 
   img= imread("../lena.jpg"); 

   // Create windows 
   namedWindow("Lena"); 

   // create Buttons 
   createButton("Blur", blurCallback, NULL, QT_CHECKBOX, 0); 

   createButton("Gray",grayCallback,NULL,QT_RADIOBOX, 0); 
   createButton("RGB",bgrCallback,NULL,QT_RADIOBOX, 1); 

   createButton("Sobel",sobelCallback,NULL,QT_PUSH_BUTTON, 0); 

   // wait app for a key to exit 
   waitKey(0); 

   // Destroy the windows 
   destroyWindow("Lena"); 

   return 0; 
} 

我们将应用三种类型的滤镜:模糊滤镜、Sobel 滤镜和颜色到灰色的转换。 所有这些都是可选的,用户可以使用我们将要创建的按钮来选择每个选项。 然后,为了获得每个过滤器的状态,我们创建了三个全局布尔变量:

bool applyGray=false; 
bool applyBlur=false; 
bool applySobel=false;

在 Main 函数中,在加载图像并创建窗口之后,我们必须使用createButton函数来创建每个按钮。

OpenCV 中定义了三种按钮类型:

  • QT_CHECKBOX
  • QT_RADIOBOX
  • QT_PUSH_BUTTON

每个按钮都有五个参数,顺序如下:

  1. 按钮名称
  2. 回调函数
  3. 指向传递给回调的用户变量数据的指针
  4. 按钮类型
  5. CheckBox 和 RadioBox 按钮类型使用的默认初始化状态

然后,我们创建一个模糊复选框按钮、两个用于颜色转换的单选按钮和一个用于 Sobel 滤镜的按钮,如以下代码所示:

   // create Buttons 
   createButton("Blur", blurCallback, NULL, QT_CHECKBOX, 0); 

   createButton("Gray",grayCallback,NULL,QT_RADIOBOX, 0); 
   createButton("RGB",bgrCallback,NULL,QT_RADIOBOX, 1); 

   createButton("Sobel",sobelCallback,NULL,QT_PUSH_BUTTON, 0); 

这些是主要功能中最重要的部分。 我们将探索Callback函数。 每个Callback更改其状态变量以调用另一个名为applyFilters的函数,以便将激活的滤镜添加到输入图像:

void grayCallback(int state, void* userData) 
{ 
   applyGray= true; 
   applyFilters(); 
} 
void bgrCallback(int state, void* userData) 
{ 
   applyGray= false; 
   applyFilters(); 
} 

void blurCallback(int state, void* userData) 
{ 
   applyBlur= (bool)state; 
   applyFilters(); 
} 

void sobelCallback(int state, void* userData) 
{ 
   applySobel= !applySobel; 
   applyFilters(); 
} 

applyFilters函数检查每个过滤器的状态变量:

void applyFilters(){ 
   Mat result; 
   img.copyTo(result); 
   if(applyGray){ 
         cvtColor(result, result, COLOR_BGR2GRAY); 
   } 
   if(applyBlur){ 
         blur(result, result, Size(5,5));     
   } 
   if(applySobel){ 
         Sobel(result, result, CV_8U, 1, 1);  
   } 
   imshow("Lena", result); 
} 

要将颜色更改为灰色,我们使用cvtColor函数,该函数接受三个参数:输入图像、输出图像和颜色转换类型。

最有用的颜色空间转换如下:

  • RGB 或 BGR 变为灰色(COLOR_RGB2GRAYCOLOR_BGR2GRAY)
  • RGB 或 BGR 到 YcrCb(或 YCC)(COLOR_RGB2YCrCbCOLOR_BGR2YCrCb)
  • RGB 或 BGR 到 HSV(COLOR_RGB2HSVCOLOR_BGR2HSV)
  • RGB 或 BGR 到 Luv(COLOR_RGB2LuvCOLOR_BGR2Luv)
  • 灰度到 RGB 或 BGR(COLOR_GRAY2RGBCOLOR_GRAY2BGR)

我们可以看到,密码很容易记住。

OpenCV works by default with the BGR format, and the color conversion is different for RGB and BGR, even when converted to gray. Some developers think that R+G+B/3 is true for gray, but the optimal gray value is called luminosity and has the formula 0,21R + 0,*72G + 0,07**B.

模糊滤镜已在上一节中进行了描述,最后,如果参数applySobel为真,我们将应用 Sobel 滤镜。 Sobel 滤波器是使用 Sobel 算子获得的图像导数,通常用于检测边缘。 OpenCV 允许我们生成不同的核大小的导数,但最常见的是用 3x3 核来计算x导数或y导数。

最重要的 Sobel 参数如下:

  • 输入图像
  • 输出图像
  • 输出图像深度(CV_8UCV_16UCV_32FCV_64F)
  • 导数x的阶数
  • 导数y的阶数
  • 内核大小(默认为 3)

要生成 3x3 内核和一阶x阶导数,我们必须使用以下参数:

Sobel(input, output, CV_8U, 1, 0);

以下参数用于y阶导数:

Sobel(input, output, CV_8U, 0, 1);      

在我们的示例中,我们同时使用xy导数,覆盖输入。 以下代码片段显示如何同时生成xy导数,在第四个和第五个参数中添加1

Sobel(result, result, CV_8U, 1, 1); 

同时应用xy导数的结果如下图所示,应用于 Lena 图片:

OpenGL 支持

OpenCV 包括 OpenGL 支持。 OpenGL 是一个图形库,作为标准集成在几乎所有图形卡中。 OpenGL 允许我们绘制 2D 到复杂的 3D 场景。 OpenCV 包括 OpenGL 支持,因为在许多任务中表示 3D 空间非常重要。 要在 OpenGL 中支持窗口,我们必须在使用namedWindow调用创建窗口时设置WINDOW_OPENGL标志。

下面的代码创建了一个支持 OpenGL 的窗口,并绘制了一个旋转平面,我们将在该平面上显示网络摄像机帧:

Mat frame; 
GLfloat angle= 0.0; 
GLuint texture;  
VideoCapture camera; 

int loadTexture() { 

    if (frame.data==NULL) return -1; 

   glBindTexture(GL_TEXTURE_2D, texture);  
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); 
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 

   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.cols, frame.rows,0, GL_BGR, GL_UNSIGNED_BYTE, frame.data); 
   return 0; 

} 

void on_opengl(void* param) 
{ 
    glLoadIdentity();   
    // Load frame Texture 
    glBindTexture(GL_TEXTURE_2D, texture);  
    // Rotate plane before draw 
    glRotatef(angle, 1.0f, 1.0f, 1.0f); 
    // Create the plane and set the texture coordinates 
    glBegin (GL_QUADS); 
        // first point and coordinate texture 
     glTexCoord2d(0.0,0.0);  
     glVertex2d(-1.0,-1.0);  
        // second point and coordinate texture 
     glTexCoord2d(1.0,0.0);  
     glVertex2d(+1.0,-1.0);  
        // third point and coordinate texture 
     glTexCoord2d(1.0,1.0);  
     glVertex2d(+1.0,+1.0); 
        // last point and coordinate texture 
     glTexCoord2d(0.0,1.0);  
     glVertex2d(-1.0,+1.0); 
    glEnd(); 

} 

int main(int argc, const char** argv) 
{ 
    // Open WebCam 
    camera.open(0); 
    if(!camera.isOpened()) 
        return -1; 

    // Create new windows 
    namedWindow("OpenGL Camera", WINDOW_OPENGL); 

    // Enable texture 
    glEnable( GL_TEXTURE_2D );
    glGenTextures(1, &texture); 
    setOpenGlDrawCallback("OpenGL Camera", on_opengl); 
    while(waitKey(30)!='q'){ 
        camera >> frame; 
        // Create first texture 
        loadTexture();     
        updateWindow("OpenGL Camera"); 
        angle =angle+4; 
    } 
    // Destroy the windows 
    destroyWindow("OpenGL Camera"); 
    return 0; 
}

让我们来理解一下代码!

第一个任务是创建所需的全局变量,我们在其中存储视频捕获、保存帧以及控制动画角度平面和 OpenGL 纹理:

Mat frame; 
GLfloat angle= 0.0; 
GLuint texture;  
VideoCapture camera; 

在我们的主要功能中,我们必须创建摄像机捕获以检索摄像机帧:

camera.open(0); 
    if(!camera.isOpened()) 
        return -1; 

如果摄像机正确打开,我们可以使用WINDOW_OPENGL标志创建支持 OpenGL 的窗口:

// Create new windows 
namedWindow("OpenGL Camera", WINDOW_OPENGL);

在我们的示例中,我们希望在平面中绘制来自网络摄像头的图像;然后,我们需要启用 OpenGL 纹理:

// Enable texture 
glEnable(GL_TEXTURE_2D); 

现在,我们已经准备好在窗口中使用 OpenGL 进行绘制,但是我们需要像典型的 OpenGL 应用一样设置一个绘制 OpenGL 回调。 OpenCV 提供了setOpenGLDrawCallback函数,该函数有两个参数-窗口名称和回调函数:

setOpenGlDrawCallback("OpenGL Camera", on_opengl); 

定义了 OpenCV 窗口和回调函数后,我们需要创建一个循环来加载纹理,更新调用 OpenGL 绘图回调的窗口内容,最后更新角度位置。 要更新窗口内容,我们使用 OpenCV 函数 UPDATE WINDOW,并将窗口名称作为参数:

while(waitKey(30)!='q'){ 
        camera >> frame; 
        // Create first texture 
        loadTexture(); 
        updateWindow("OpenGL Camera"); 
        angle =angle+4; 
    } 

当用户按下Q键时,我们就处于循环中。在编译我们的应用示例之前,我们需要定义loadTexture函数和我们的on_opengl回调绘制函数。 loadTexture函数将我们的Mat帧转换为 OpenGL 纹理图像,以便在每个回调绘图中加载和使用。 在将图像作为纹理加载之前,我们必须确保帧矩阵中有数据,并检查数据变量对象是否为空:

if (frame.data==NULL) return -1; 

如果矩阵帧中有数据,则可以创建 OpenGL 纹理绑定并将 OpenGL 纹理参数设置为线性插值:

glGenTextures(1, &texture); 

glBindTexture(GL_TEXTURE_2D, texture); 
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); 
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

现在,我们必须定义像素在矩阵中的存储方式,并使用 OpenGLglTexImage2D函数生成像素。 需要注意的是,默认情况下,OpenGL 使用 RGB 格式,OpenCV 使用 BGR 格式,我们必须在此函数中设置正确的格式:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.cols, frame.rows,0, GL_BGR, GL_UNSIGNED_BYTE, frame.data); 
    return 0; 

现在,当我们在主循环中调用updateWindow时,我们只需要在每次回调时完成绘制平面。 我们使用常用的 OpenGL 函数,然后加载标识 OpenGL 矩阵以重置之前的所有更改:

glLoadIdentity();   

我们还必须将框架纹理带到记忆中:

    // Load Texture 
    glBindTexture(GL_TEXTURE_2D, texture);  

在绘制平面之前,我们将所有变换应用于场景。 在我们的示例中,我们将沿1,1,1轴旋转平面:

    // Rotate plane 
    glRotatef(angle, 1.0f, 1.0f, 1.0f); 

现在我们已经正确设置了绘制平面的场景,我们将绘制四边形的面(具有四个顶点的面),并为此使用glBegin (GL_QUADS)命令:

// Create the plane and set the texture coordinates 
    glBegin (GL_QUADS); 

接下来,我们将绘制一个以第二0,0位置为中心的平面,该平面的大小为 2 个单位。 然后,我们必须使用glTextCoord2DglVertex2D函数定义要使用的纹理坐标和顶点位置:

    // first point and coordinate texture 
 glTexCoord2d(0.0,0.0);  
 glVertex2d(-1.0,-1.0);  
    // seccond point and coordinate texture 
 glTexCoord2d(1.0,0.0);  
 glVertex2d(+1.0,-1.0);  
    // third point and coordinate texture 
 glTexCoord2d(1.0,1.0);  
 glVertex2d(+1.0,+1.0); 
    // last point and coordinate texture 
 glTexCoord2d(0.0,1.0);  
 glVertex2d(-1.0,+1.0); 
    glEnd(); 

This OpenGL code becomes obsolete, but it is appropriated to understand better the OpenCV and OpenGL integration without complex OpenGL code. By way of an introduction to modern OpenGL, read Introduction to Modern OpenGL, from Packt Publishing.

我们可以在下图中看到结果:

简略的 / 概括的 / 简易判罪的 / 简易的

在本章中,我们学习了如何使用 OpenGL 创建不同类型的用户界面来显示图像或 3D 界面。 我们学习了如何创建滑块和按钮,或者如何在 3D 中绘图。 我们也通过原生 OpenCV 学习了一些基本的图像处理过滤器,但是有一些新的开源过滤器可以让我们添加更多功能,比如 CVUI(https://dovyski.github.io/cvui/)或 OpenCVGUI(https://damiles.github.io/OpenCVGUI/)。

在下一章中,我们将构建一个完整的照片工具应用,在其中我们将应用到目前为止所学的所有知识。 通过图形用户界面,我们将学习如何对输入图像应用多个滤镜。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组