2.3.3输出数据自动分配内存

1.代码功能描述

这段 C++ 代码使用 OpenCV 库实现了从摄像头捕获视频流,并对每一帧图像进行处理,将其转换为灰度图像,然后进行高斯模糊和 Canny 边缘检测,最后在窗口中实时显示处理后的边缘图像。当用户按下任意键时,程序会停止运行。

#include<opencv2/opencv.hpp>
#include<opencv2/highgui.hpp>
using namespace cv;
int main(int,char**)
{
	VideoCapture cap(0);
	if (!cap.isOpened()) return -1;
	Mat frame, edges;
	namedWindow("edges", 1);
	for (;;) {
		cap >> frame;
		cvtColor(frame, edges, COLOR_BGR2GRAY);
		GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
		Canny(edges, edges, 0, 30, 3);
		imshow("edges", edges);
		if (waitKey(30) >= 0)
			break;
	}
	return 0;
}

2.代码逐行解释

(1)OpenCV 库的头文件

#include<opencv2/opencv.hpp>
#include<opencv2/highgui.hpp>
  • opencv2/opencv.hpp 是一个综合性的头文件,包含了 OpenCV 的大部分核心功能;
  • opencv2/highgui.hpp 含用于创建窗口、显示图像和处理用户输入的高级图形用户界面相关的功能。

 (2) using 指令的使用

using namespace cv;
  • 将 cv 命名空间中的所有标识符引入到全局命名空间中,这样在后续代码中就可以直接使用 OpenCV 的类和函数,而不需要加上 cv:: 前缀

 (3) 程序的入口函数 main

int main(int,char**)
{
  • int 表示函数返回一个整数值
  • (int, char**) 表示函数接受两个参数,但这里没有使用它们的具体名称,意味着在函数内部不会使用这两个参数。
  • char** C 和 C++ 中是一个指向字符指针的指针,常用于表示字符串数组或者作为 main 函数的参数来接收命令行参数。

(4)VideoCapture打开摄像头

    VideoCapture cap(0);
    if (!cap.isOpened()) return -1;
  • VideoCapture cap(0);:创建一个 VideoCapture 对象 cap,并尝试打开默认的摄像头设备(设备编号为 0)。
  • if (!cap.isOpened()) return -1;检查摄像头是否成功打开
  • 如果打开失败,cap.isOpened() 会返回 false,此时程序会返回 -1 并终止。

VideoCapture 并不是一个函数,而是 OpenCV 库中用于处理视频捕获的一个类。这个类提供了一系列的方法和属性,可用于从摄像头、视频文件或网络视频流中读取视频帧。下面为你详细介绍 VideoCapture 类的相关内容。

VideoCapture

VideoCapture 类主要用于捕获视频数据,支持多种视频源,包括:

  • 摄像头设备:可以连接到计算机的摄像头,实时捕获视频流。
  • 视频文件:读取本地存储的视频文件,如 .mp4.avi 等格式。
  • 网络视频流:通过网络协议(如 RTSP)获取远程视频流。

构造函数

VideoCapture 类有几个常用的构造函数:

  • VideoCapture():默认构造函数,创建一个未初始化的 VideoCapture 对象,后续需要使用 open() 方法来打开视频源。
  • VideoCapture cap;
    cap.open(0); // 打开默认摄像头
  • VideoCapture(int index):通过设备索引打开摄像头设备。index 是摄像头的编号,通常默认摄像头的编号为 0,如果有多个摄像头,可以使用不同的编号来选择。
  • VideoCapture cap(0); // 打开默认摄像头
  • VideoCapture(const std::string& filename):通过视频文件的路径或网络视频流的 URL 来打开视频源。
  • cv::VideoCapture cap("path/to/video.mp4"); // 打开本地视频文件

常用方法

  • bool isOpened()检查视频捕获设备或文件是否成功打开。如果成功打开,返回 true;否则返回 false
  • VideoCapture cap(0);
    if (!cap.isOpened()) {
        cerr << "无法打开摄像头" << endl;
        return -1;
    }
  • VideoCapture& operator>>(Mat& image):从视频源中读取一帧图像,并将其存储在 Mat 对象中。这个操作符重载可以方便地使用 >> 符号来读取视频帧。
  • Mat frame;
    cap >> frame; // 读取一帧图像
  • bool read(Mat& image):与 operator>> 功能类似,也是从视频源中读取一帧图像并存储在 Mat 对象中。如果成功读取到图像,返回 true;否则返回 false
  • cv::Mat frame;
    bool success = cap.read(frame);
    if (!success) {
        std::cerr << "无法读取视频帧" << std::endl;
    }
  • void release():释放视频捕获设备或文件的资源。在不再使用 VideoCapture 对象时,应该调用这个方法来释放资源。
  • cap.release(); // 释放资源

(5)定义Mat对象

  Mat frame, edges;
  • 定义两个 Mat 对象,frame 用于存储从摄像头捕获的每一帧原始图像edges 用于存储处理后的边缘图像

(6)创建"edges" 窗口

namedWindow("edges", 1);
  • 用于显示处理后的边缘图像。参数 1 表示窗口可以调整大小。
namedWindow 函数

namedWindow 函数属于 OpenCV 的 highgui 模块,该模块主要负责图形用户界面相关的操作,像窗口创建、图像显示、鼠标键盘事件处理等。借助 namedWindow 函数,你能够创建一个指定名称的窗口,后续可以在这个窗口里显示图像或者视频帧。

函数原型

在 OpenCV 4.x 及之后的版本中,namedWindow 函数的原型如下:

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

参数解释

  • winname:类型为 const String&,实际上 String 是 std::string 的别名,所以这里传入的是一个字符串。此参数代表要创建的窗口的名称,该名称在整个程序里必须是唯一的,后续对窗口进行操作(如显示图像、调整窗口大小等)时,会依据这个名称来识别窗口。
  • flags:类型为 int,是一个可选参数,默认值为 WINDOW_AUTOSIZE。它用于指定窗口的属性,常见的取值及含义如下:
    • WINDOW_KEEPRATIO:显示图像时,保持图像的纵横比(默认行为)。
    • WINDOW_FREERATIO:显示图像时,不保持图像的纵横比。
    • WINDOW_AUTOSIZE:窗口大小会根据显示的图像自动调整,用户无法手动改变窗口大小。
    • WINDOW_NORMAL:可以手动调整窗口的大小。

注意事项

  • 窗口名称唯一性:每个窗口的名称必须是唯一的,如果尝试创建一个已经存在的窗口名称,不会创建新的窗口,而是会复用已有的窗口。
  • 资源管理:在程序结束时,建议用 cv::destroyAllWindows() 或 cv::destroyWindow(const String& winname) 来销毁创建的窗口,释放相关资源
  • 显示图像顺序namedWindow 函数通常需要在 imshow 函数之前调用,不过在某些情况下,如果省略 namedWindow 函数,imshow 函数会自动创建一个默认属性的窗口。

 (7)重复捕捉视频帧

    for (;;) {
        cap >> frame;
  • 这是一个无限循环,用于不断从摄像头捕获视频帧。
  • cap >> frame; 是将当前摄像头捕获的一帧图像读取到 frame 对象中

(8)灰度图像及空间转化

        cvtColor(frame, edges, COLOR_BGR2GRAY);
  • 使用 cvtColor 函数将 frame 中的彩色图像转换为灰度图像,并将结果存储在 edges 中。
  • COLOR_BGR2GRAY 表示从 BGR 颜色空间转换为灰度颜色空间
cvtColor 函数
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn = 0);

参数解释

  • src:类型为 InputArray,表示输入的图像。它可以是 cv::Mat 类型的对象,代表要进行颜色空间转换的原始图像。
  • dst:类型为 OutputArray,表示输出的图像。转换后的图像将存储在这个对象中,通常也是 cv::Mat 类型。
  • code:类型为 int,是一个非常关键的参数,用于指定颜色空间转换的代码。OpenCV 提供了众多预定义的转换代码,常见的如下:
    • cv::COLOR_GRAY2BGR:将灰度图像转换为 BGR 颜色空间的图像
    • cv::COLOR_BGR2HSV:将 BGR 颜色空间的图像转换为 HSV 颜色空间的图像
    • cv::COLOR_BGR2GRAY:将 BGR(Blue, Green, Red)颜色空间的图像转换为灰度图像
  • dstCn:类型为 int,是一个可选参数,默认值为 0。它用于指定输出图像的通道数。如果为 0,则输出图像的通道数将根据转换代码自动确定。

(9)高斯模糊处理

        GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
  • 对灰度图像 edges 进行高斯模糊处理,以减少图像中的噪声。
  • Size(7, 7) 表示高斯核的大小为 7x7,两个 1.5 分别表示 X 和 Y 方向的高斯核标准差
函数原型
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT);

参数解释

  • src:类型为 InputArray,表示输入的图像。它通常是一个 cv::Mat 对象,即要进行高斯模糊处理的原始图像。该图像可以是单通道(如灰度图像)或多通道(如彩色图像)。
  • dst:类型为 OutputArray,表示输出的图像。经过高斯模糊处理后的图像将存储在这个对象中,同样一般是 cv::Mat 类型。其尺寸和类型与输入图像相同。
  • ksize:类型为 Size,表示高斯核的大小Size 是一个包含两个整数的结构体,分别表示高斯核的宽度和高度,如 Size(3, 3) 表示一个 3x3 的高斯核。这两个值必须是正奇数,如果为 0,则会根据 sigmaX 和 sigmaY 自动计算核的大小
  • sigmaX:类型为 double,表示高斯核在 X 方向上的标准差。标准差决定了高斯核的形状,较大的标准差会使模糊效果更明显
  • sigmaY:类型为 double,是一个可选参数,默认值为 0。表示高斯核在 Y 方向上的标准差。如果该值为 0,则会将其设为与 sigmaX 相同的值。
  • borderType:类型为 int,是一个可选参数,默认值为 BORDER_DEFAULT。它用于指定图像边界的处理方式。在进行卷积操作时,图像边界的像素没有完整的邻域,需要采用特定的方法来处理这些边界像素,常见的处理方式包括复制边界像素、镜像边界像素等。

(10)Canny 函数对模糊后的图像进行边缘检测

        Canny(edges, edges, 0, 30, 3);
  • 0 和 30 分别是 Canny 算法的低阈值和高阈值3 表示使用的 Sobel 算子的孔径大小
Canny 函数
void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false);

参数解释

  • image:类型为 InputArray,表示输入的图像。通常要求输入图像为单通道的灰度图像,因为 Canny 算法是基于灰度值的变化来检测边缘的。可以是 cv::Mat 类型的对象。
  • edges:类型为 OutputArray,表示输出的边缘图像。检测到的边缘将存储在这个对象中,同样一般为 cv::Mat 类型。输出图像是一个二值图像,边缘像素值为 255,非边缘像素值为 0。
  • threshold1:类型为 double,是 Canny 算法中的低阈值。在双阈值处理阶段,用于判断像素是否可能为边缘像素。低于该阈值的像素会被认为不是边缘像素。
  • threshold2:类型为 double,是 Canny 算法中的高阈值。高于该阈值的像素会被认为是强边缘像素,直接保留。
  • apertureSize:类型为 int,是一个可选参数,默认值为 3。它表示 Sobel 算子的孔径大小,用于计算图像的梯度。孔径大小必须是 3、5 或 7,分别对应不同的 Sobel 核大小。
  • L2gradient:类型为 bool,是一个可选参数,默认值为 false。用于指定是否使用更精确的 L2 范数来计算梯度幅值。如果设置为 true,则使用 L2 范数计算;如果设置为 false,则使用近似的 L1 范数计算L1 范数计算速度更快,但 L2 范数计算结果更精确
Canny 边缘检测算法步骤
  1. 高斯平滑使用高斯滤波器对输入图像进行平滑处理,以减少图像中的噪声。在调用 Canny 函数之前,通常需要手动进行这一步,或者在调用时输入已经经过高斯平滑的图像。
  2. 计算梯度幅值和方向:使用 Sobel 算子计算图像在水平和垂直方向上的梯度,然后计算梯度幅值和方向
  3. 非极大值抑制:对梯度幅值进行非极大值抑制,以细化边缘。即只保留局部梯度幅值最大的像素,将其他像素置为 0。
  4. 双阈值处理和边缘连接:使用两个阈值(threshold1 和 threshold2)对边缘像素进行分类。高于高阈值的像素被认为是强边缘像素,低于低阈值的像素被认为不是边缘像素,介于两个阈值之间的像素如果与强边缘像素相连,则被认为是边缘像素,否则不是。

(11)显示处理后的边缘图像

        imshow("edges", edges);
  • 在之前创建的名为 "edges" 的窗口中显示处理后的边缘图像。

(12)检查是否有按键事件发生

        if (waitKey(30) >= 0)
            break;
  • waitKey(30) 会等待 30 毫秒,检查是否有按键事件发生。
  • 如果有按键按下,waitKey 会返回按键的 ASCII 码值(大于等于 0),此时程序会跳出循环,结束视频捕获和处理。

(13)退出循环

    }
    return 0;
}
  • 循环结束后,程序返回 0,表示正常退出。

3.总结

这段代码通过一系列 OpenCV 函数,实现了从摄像头捕获视频、图像预处理(颜色转换、高斯模糊)、边缘检测以及结果显示的完整流程。各函数相互协作,完成了实时视频流的边缘检测任务。同时,代码中使用的 cv::Mat 对象会自动管理内存,避免了手动内存管理的复杂性和潜在风险

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐