opencv_C++学习笔记(入门30讲)

文章目录

  • 1.配置开发环境
  • 2.图像读取与显示
  • 3.图像色彩空间转换
  • 4.图像对象的创建与赋值
  • 5.图像像素的读写操作
  • 6.图像像素的算数操作
  • 7.滚动条-调整图像亮度
  • 8.滚动条-调整对比度和亮度
  • 9.键盘响应操作
  • 10.图像像素的逻辑操作
  • 11.图像的通道分离和合并
  • 12.图像色彩空间转换
  • 13.图像的像素值统计
  • 14.图像几何形状绘制
  • 15.随机数与随机颜色
  • 16.多边形填充与绘制
  • 17.鼠标操作与响应
  • 18.图像像素类型转换与归一化
  • 19.图像缩放与插值
  • 20.图像翻转
  • 21.图像旋转
  • 22.视频文件摄像头使用
  • 23.视频处理与保存
  • 24.图像直方图
  • 25.二维直方图
  • 26.直方图均衡化
  • 27.图像卷积和高斯模糊
  • 28.双边模糊
  • 29.人脸实时检测

1.配置开发环境

配置开发环境提前需要安装好Visual Studio和opencv包,这里可以单独观看视频学习安装。
以下过程参考链接: B站opencv快速入门30讲-贾志刚

  1. 配置包含目录
    这里需要注意上面的菜单栏选择Release和x64,打开属性管理器选择Release|x64>VC++目录>包含目录。(可能由于Visual Studio版本的问题,这里没有和视频中相同的microsoft.cpp.x64.user文件(但是新建的相同文件名的文件会显示已存在该文件,在项目中确实已经存在),因此直接对Release|x64属性修改是一样的)
    具体步骤:视图》其他窗口》属性管理器》Release | x64》属性》VC++目录》包含目录》编辑》将opencv安装包中的两个目录地址复制进去,如下图所示:这样包含目录就配置好了。
    在这里插入图片描述在这里插入图片描述

  2. 配置库目录
    接下来配置库目录,点击常规下面的库目录》编辑》如下图所示将opencv安装包的lib 目录复制进去,点击确定。这样库目录就配置好了。
    在这里插入图片描述

  3. 配置链接器
    接下来配置链接器:如下图所示:属性》链接器》输入》附加依赖项》编辑》,在下图中有两个.lib 文件opencv_world460.lib 和opencv_world460d.lib,分别对应Release和Debug,切记勿将两个文件同时写进去。这里选择opencv_world460.lib。点击确定。这样链接器配置完毕。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  4. 配置环境变量并重启VS2022
    系统》高级系统设置》配置环境变量,将opencv安装包里面的类似下图的bin路径添加进去,点击确定。重启VS。在这里插入图片描述

配置环境变量并重启VS2022

2.图像读取与显示

#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;
using namespace std;int main(int argc, char ** argv){cv::Mat src = cv::imread("C/code/workspace/lena.jpg", IMREAD_GRAYSCALE);nameWindow("输入窗口“, WINDOW_FREERATIO);if (src.empty()) {printf("could not load image...");return -1;}cv::imshow("输入窗口”, src);cv::watiKey(0);destoryAllWindows();return 0;
}

代码解释:
#<opencv2/opencv.hpp> OpenCV库预处理指令,包含了OpenCV库的所用头文件。
# C++标准库预处理指令。该头文件包含了C++库中的输入输出流函数。
using namespace cv 这是openCV库的命名空间。它将所有OpenCV库中的函数都放在cv命名空间中,这样可以通过cv:: 函数来调用它们。
suing namespace std 这是C++标准库的命名空间,它将C++库中的函数都放在std命名空间中,这样可以通过std:: 函数来调用它们。
int main(int argc, char** argv) 这是程序主函数接受两个参数,argc和argv,argc是一个整数,表示命令行参数的个数,argv是一个字符淑珍数组,表示命令行参数的指针。
cv::Mat src = cv::imread(“C/code/workspace/lena.jpg”, IMREAD_GRAYSCALE); cv::imread()是OpenCV读取图像的函数,将图像读取为灰度图像,并将图像存储在cv::Mat 的src变量中。
nameWindow(“输入窗口”,WINDOW_FREERATIO)这是OpenCV库中创建窗口的函数指令,将创建一个名为“输入窗口”窗口,并使用WINDOW_FREERATIO自动调整窗口大小。
cv::imshow(“输入窗口”, src) 这是OpenCV中显示图像的函数,它将变量src显示在“输入窗口”中。
cv::waitKey(0) 这是OpenCV中的waitKey函数等待用户按键的指令,它将等待用户按下任意键,然后继续执行。
destroyAllWindows() 这是OpenCV关闭所有窗口的指令。

3.图像色彩空间转换

这节学习如何创建头文件,如何定义头文件中的函数,以及如何在程序中调用自定义的头文件函数

  1. 头文件的创建
    创建头文件:在项目文件下面的头文件中创建一个新建项,名为xxxxx.h 的文件
    #include <opencv2/opencv.hpp>
    using namespace cv;
    class QuickDemo {public:void colorSpace_Demo(Mat& image);
    };
    
    代码解释:定义了一个名为QuickDemo 的类,public 是类的公共部分的开始, void colorSpace_Demo( )是QuickDemo类的成员函数,这个函数接受一个Mat类型的参数 image,用于存储输入的图像。};是类的定义的结束。
  2. 定义头文件函数
    头文件中定义的类成员函数需要在源文件中创建一个名为 xxxxx.cpp 的文件
    #include "quickopencv.h"
    void QuickDemo::colorSpace_Demo(Mat& image) {Mat gray, hsv;cvtColor(image, hsv, COLOR_BGR2HSV);cvtColor(image, gray, COLOR_BGR2GRAY);imshow("HSV", hsv);imshow(“灰度”, gray);imwrite("C/code/workspace/hsv.jpg", hsv);imwrite("C/code/workspace/gray.jpg", gray);
    }
    
    代码解释:QuickDemo::colorSpace_Demo(Mat& image)): 这是QuickDemo类的成员函数colorSpace_Demo的声明。cvtColor() 是OpenCV库进行颜色空间转化的指令。imwrite()这是OpenCV的imwrite函数将图像保存到文件的指令。
    HSV(Hue, Saturation, Value)是一种颜色的表示方式,色相(Hue),饱和度(Saturation), 透明度(Value)
  3. 主函数文件
    主函数文件在源文件中 新建项为文件名 xxxxxx.cpp 的文件
    #include <opencv2/opencv.hpp>
    #inluce <iostream>
    #include "quickopencv"
    using namespace cv;
    using namespace std;
    int main(int argc, char ** argv){Mat src = cv::imread("C/code/workspace/lena.jpg");nameWindow("输入窗口“, WINDOW_FREERATIO);if(src.empty()) {printf("could not load image...");return -1;}cv::imshow("输入窗口”,src);QuickDemo qd;qd.colorSpace_Demo(src);cv::waitKey(0);destroyAllWindows();return 0;
    }
    

4.图像对象的创建与赋值

这一节承接上一节的内容,需要在自定义的头文件中添加类QuickDemo 的成员函数mat_creation_demo(), 同时需要在源文件夹下面的quickdemo.cpp 中定义函数Quick::mat_creation_demo() 的具体内容。本节中学习的关于OpenCV库中的克隆、复制、赋值、创建空白图像等都是在成员函数mat_creation_demo()中定义的。在执行程序时还需要再main.cpp 文件中调用mat_creation_demo()函数。
下面是关于成员函数的定义:

void QuickDemo::mat_creation_demo(Mat& image){Mat src = image;// 创建方法-克隆Mat m1 = src.clone();//复制Mat m2;src.copyTo(m2);//赋值法Mat m3 = srcl;//创建空白图像Mat m4 = Mat::zeros(src.size(), src.type());Mat m5 = Mat::zeros(size(512, 512), CV_8UC3);imshow("窗口1", m1);imshow(“窗口2, m2);imshow("窗口3", m3);// 除上面的图像对象外,还可以创建一个空白对像,然后赋值标量Mat m6, m7;m6 = image.clone();image.copyTo(m7);//创建单通道的空白图像Mat m8 = Mat::zeros(Size(512, 512), CV_8UC1);//创建三通道空白图像Mat m9 = Mat::zeros(Size(512, 512), CV_8UC3);//给三通道空白图像赋标量值m9 = Scalar(0, 128, 64);std::cout << "width:" << m9.cols << "height:" << m9.rows << "channels:" << m9.channels() << std::endl;std::cou t  << m9 <<std::endl;

代码解释:上面的代码中主要是最后两行的输出流需要注意,m9.cols:这是m9 对象的成员变量,表示图像的宽度。m9.rows:这是m9 对象的成员变量,表示图像的高度。m9.channels():这是m9 对象的成员变量,用于获取图像的通道数。std::cout:这是C++标准库中的iostream流对象,用于输出文本,<< :这是流对象的输出操作符,用于将右边的操作数(如变量m9.cols)输出到流中。std::endl:这是iostream流对象的结束标记,用于输出一个换行符,并刷新输出缓冲区。

5.图像像素的读写操作

第一种方法是通过数组坐标访问每一像素,总体来看是使用了两个for循环;同样的,这里是在类成员是函数的详细定义中,去定义这个像素的访问。下面是第一种方法的代码演示:

void QuickDemo::pixel_visit_demo(Mat& image) {int w = image.cols;int h = image.rows;int dims = image.channels();for (int row = 0; row < h; row++) {for (int col = 0; col < w; col++) {if (dims == 1) {  //灰度图像int pv = image.at<uchar>(row, col);image.at<uchar>(row, col) = 255 - pv;}if (dims == 3) {  //彩色图像Vec3b bgr = image.at<Vec3b>(row, col);image.at<Vec3b>(row, col)[0] = 255 - bgr[0];image.at<Vec3b>(row, col)[0] = 255 - bgr[1];image.at<Vec3b>(row, col)[0] = 255 - bgr[2];}}}imshow("像素读写演示", image);

第二种方法是通过指针的方式访问每个像素,下面是代码演示:

void QuickDemo::pixel_visit_demo(Mat& image) {int w = image.cols;int h = image.rows;int dims = image.channels();for (int row = 0; row < h; row++) {uchar* current_row = image.ptr<uchar>(row);for (int col = 0; col < w; col++) {if (dims == 1) {  //灰度图像int pv = *current_row;*current_row++ = 255 - pv;}if (dims == 3) {  //彩色图像*current_row++ = 255 - *current_row;*current_row++ = 255 - *current_row;*current_row++ = 255 - *current_row;}}}imshow("像素读写演示", image);

代码解释:这里主要是需要理解指针的运用,image.ptr(row):这是OpenCV中的Mat对象image的成员函数ptr,它返回抑制指向图像第row行的数据的指针。表示我们想要获取的像素数据类型是8位无符号整数(uchar).插入一句额外的话,C语言之所以效率高,就是因为C可以像汇编一样去操控内存。整形(int)是占用4个字节,基于32位的有符号整形。
下面是关于指针的代码图解
在这里插入图片描述

6.图像像素的算数操作

这节学习如何对图像的像素进行加减乘除操作,下面通过代码演示加深对图像像素进行加减乘除的理解。

void QuickDemo::operators_demo(Mat &image){Mat dst = Mat::zeros(image.size(), image.type());Mat m = Mat::zeros(image.size(), image.type());//dst = image - Scalar(50, 50, 50);//dst = image + Scalar(50, 50 ,50);//mutiply(image, m, dst);// dst = image / Scalar(50, 50, 50);// imshow("图像的算数操作",  dst);int w = image.cols;int h = image.rows;int dims = image.channels();for (int row = 0; row < h; row++){for (int col = 0; col < w; col++){Vec3b p1 = image.at<Vec3b>(row, col);Vec3b p2 = m.at<Vec3b>(row, col);dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[2]);}}imshow("加法操作", dst);
}

代码解释:at(row, col)表示获取指定位置的像素,获得的p1 是一个有三个元素的数组,saturate_cast是将相加后的像素值转换为8位无符号(uchar)的整数,以适应dst图像的像素范围。

7.滚动条-调整图像亮度

这节学习通过滚动条,用户可以在窗口中自行调整图像的亮度,下面是代码演示:

Mat src, dst, m;
int lightness = 20;
static void on_track(int, void*) {m = Scalar(lightness, lightness, lightness);add(src, m, dst);//subtract(src, m, dst);imshow("亮度调整", dst);
}
void QuickDemo::tracking_bar_demo(Mat& image) {namedWindow("亮度调整", WINDOW_AUTOSIZE);dst = Mat::zeros(image.size(), image.type());m = Mat::zeros(image.size(), image.type());src = image;int max_value = 250;createTrackbar("Value Bar:", "亮度调整", &lightness, max_value, on_track);on_track(50, 0);
}	 

代码解释:这节的代码和下一节一起进行解释。

8.滚动条-调整对比度和亮度

这节学习在窗口中添加两个滚动条,一个调整图像的亮度,另一个调整图像的对比度,下面是代码演示:

static void on_lightness(int b, void* userdata){Mat image = *((Mat*)userdata);Mat dst = Mat::zeros(image.size(), image.type());Mat m = Mat::zeros(image.size(), image.type());addWeighted(image, 1.0, m, 0, b, dst);imshow("亮度与对比度调整",  dst);
}
static void on_contrast(int b, void* userdata){Mat image = *((Mat*)userdata);Mat dst = Mat::zeros(image.size(), image.type());Mat m = Mat::zeros(image.size(), image.type());double contrast = b / 100.0;addWeighted(image, contrast, m, 0.0, 0, dst);imshow("亮度与对比度调整",  dst);
}void QuickDemo::tracking_bar_demo(Mat &image){nameWindow("亮度与对比度调整", WINDOW_AUTOSIZE);int lightness = 50;int max_value = 250;int contrast_value = 100;createTrackbar("Value Bar:", "亮度与对比度调整“, &lightness,max_value, on_lightness, (void*)(&image));createTrackbar("Contrast Bar:", "亮度与对比度调整“, &contrast_value,200, on_contrast, (void*)(&image));on_lightness(50, &image);
}

代码解释:在回调函数on_lightness()中,static: 表示该函数是静态的,只在当前文件中可见。除此之外,还有静态变量,静态函数,静态类。定义了变量的周期,在静态函数中,只在当前文件中可见,防止其他文件有重名的函数。void:表示函数的返回类型为空;on_lightness:是函数名;int b:表示亮度调整参数;void* userdata:一个指向用户数据的指针,通常用于传递图像数据;
((Mat)userdata): 将userdata指针转换为Mat* 类型并解引用,获取图像数据。addWeighted(image, 1.0, m, 0, b, dst); addWeighted: OpenCV中的函数,用于图像的加权叠加。该函数的公式为:dst = image * 1.0 + m * 0 + b,即 dst = image + b,实现了亮度的调整。
最后定义了一个名为tracking_bar_demo的函数,用于创建一个窗口并添加了两个滑动条(Tackbar),分别用于调整图像的亮度和对比度。createTrackbar: OpebCV中的函数,用于创建一个滑动条。Value Bar: :滑动条的标签。&lightness: 指向亮度值的指针,滑动条的当前值。max_value: 滑动条的最大值。on_lightness: 回调函数,当滑动条值改变时调用。(void*)(&image): 传递给回调函数的用户数据,即图像数据

9.键盘响应操作

这节学习通过键盘按键对图像进行操作并显示操作后的结果

void QuickDemo::key_demo(Mat& image) {Mat dst = Mat::zeros(image.size(), image.type());;while (true) {int c = waitKey(100);if (c == 27) {//Escbreak;}if (c == 49) {//Key#1std::cout << "you enter Key#1" << std::endl;cvtColor(image, dst, COLOR_BGR2GRAY);}if (c == 50) {// Key#2std::cout << "you enter Key#2" << std::endl;cvtColor(image, dst, COLOR_BGR2HSV);}if (c == 51) {//Key#3std::cout << "you enter Key#3" << std::endl;dst = image + Scalar(0, 128, 0);}//std::cout << c << std::endl;imshow("键盘响应", dst);}
}

代码解释:需要注意的是函数waitKey()返回的数据类型,总体上代码不难。

10.图像像素的逻辑操作

这节学习创建两个图像,对这两个图像进行与、或、非 操作;下面是代码演示:

void QuickDemo::bitwise_demo(Mat& image) {Mat m1 = Mat::zeros(Size(256, 256), CV_8UC3);Mat m2 = Mat::zeros(Size(256, 256), CV_8UC3);rectangle(m1, Rect(100, 100, 80, 80), Scalar(255, 255, 0), -1, LINE_8, 0);rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), -1, LINE_8, 0);imshow("m1", m1);imshow("m2", m2);Mat dst;//Mat dst = ~image;//bitwise_not(image, dst);//bitwise_or(m1, m2, dst);bitwise_xor(m1, m2, dst);imshow("像素位操作", dst);
}

代码解释:rectangle() 是OpenCV库中的函数,在图像上绘制一个填充的矩形。m1表示目标图像,即在m1上进行绘制,Rect() 这是矩形的参数,用于确定矩形的大小和位置;Scalar()这是矩形的颜色,-1表示矩形的厚度,这里就表示对矩形进行填充,除此之外还有其他参数,例如0,1,2;LINE_8这是绘制矩形时使用的线条类型,LINE_8表示8连通线型。0:这是可选的坐标缩放因子。

11.图像的通道分离和合并

这节学习将RGB图像的三个通道分离,并以单独的红色,绿色和蓝色进行显示;这是通道分离,通道合并时,学习如何将两个任意的通道进行合并并显示出来。下面时代码演示:

void QuickDemo::channels_split_demo(Mat& image) {std::vector<Mat> mv;split(image, mv);imshow("蓝色", mv[0]);imshow("绿色", mv[1]);imshow("红色", mv[2]);Mat dst;mv[1] = 0;mv[2] = 0;merge(mv, dst);imshow("蓝色", dst);int from_to[] = { 0, 2, 1, 1, 2, 0 };mixChannels(&image, 1, &dst, 1, from_to, 3);imshow("通道混合", dst);
}

代码解释:split() 函数用于将图像分离成三个通道并存储在mv变量中,这样根据数组的操作可以对单独的每个通道进行显示,merge() 函数用于将两个图像进行合并,合并前将其余的两个通道的数值置零就可以以单独的红、蓝、绿进行显示;最后用到一个通道混合的函数mixChannels(),mixChannels是OpenCV库中的一个函数,void mixChannels(const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs);
用于将输入图像的某些通道赋值到输出图像的某些通道中。const Mat* src是输入图像数组,size_t nsrcs是输入图像的数量,Mat* dst输出图像数组,可以是一个或多个图像。 size_t ndsts是输出图像数量。const int* fromTo:一个数组,指定输入和输出通道的映射关系。 size_t npairs是数组中映射对的数量。

12.图像色彩空间转换

这节学习如何从纯色的背景中扣出前景然后对背景颜色进行转换。需要注意的是背景颜色必须是纯色的。下面是代码演示以及实验结果图。

void QuickDemo::inrange_demo(Mat& image){Mat hsv;cvtColor(image, hsv, COLOR_BGR2HSV);Mat mask;inRange(hsv, Scalar(36, 43, 46), Scalar(77, 255, 255), mask);imshow("mask", mask);Mat redback = Mat::zeros(image.size(), image.type());redback = Scalar(40, 40, 200);bitwise_not(mask, mask);imshow("mask", mask);image.copyTo(redback, mask);imshow("roi区域提取", redback);
}

代码解释:inRange()是OpenCV库中的函数,用于在HSV颜色空间中进行颜色阈值处理。具体来说,它将图像中的像素值与给定的范围进行比较,并将符合条件的像素设置为白色(255), 不符合条件的像素设置为黑色(0)。HSV颜色空间将颜色分解为色调(Hue)、饱和度(Saturation)和亮度(Value)三个分量;在上述代码中hsv是输入的图像,Scalar(36, 43, 46): 是下限阈值,这里的阈值根据HSV颜色空间能够进行查询获得,这里的三个数是绿色的阈值下限。Scalar(77, 255, 255)是绿色的阈值上限。mask:输出图像,是一个二值化的图像,在这个图像中符合颜色范围的像素值为255,不符合的像素值为0。后面将掩码取反操作,即前景图像的像素值从原来的0变为255,背景像素值从原来的255变为0;接下来使用image.copyTo(redback, mask);这里使用掩码复制掩码中像素值为255的redback像素值。这里更深入的理解需要理解copyTo()函数。

13.图像的像素值统计

这节学习如何计算图像各通道的均值和方差。下面是代码演示:

void QuickDemo::pixel_statistic_demo(Mat& image) {double minv, maxv;Point minLoc, maxLoc;std::vector<Mat>mv;split(image, mv);for (int i = 0; i < mv.size(); i++) {minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat());std::cout <<"No.channels: "<< i <<"min value : " << minv << "max value : " << maxv << std::endl;}Mat mean, stddev;meanStdDev(image, mean, stddev);std::cout << "means:" << mean << std::endl;std::cout<< "stddev:" << stddev << std::endl;
}

代码解释:首先定义了两个Point类型的变量分别命名为minLoc, mavLoc;;其次定义了一个std::vector类型的变量mv,其元素类型为Mat ,这里的mv可以用于存储多个图像或矩阵数据。.Point是OpenCV库中定义的一个类,通常用于表示图像中的二维坐标点(x, y)。minMaxLoc()是OpenCV库中的函数,用于在给定的图像或矩阵中查找最小值和最大值及其位置。minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat()); ,mv[i]:是输入的数据,&minv这是指向double类型的指针,用于存储找到的最小值。&minLoc这是指向Point类型的指针,用于存储最小值的位置,Mat(): 这是一个空的Mat对象,表示掩码(mask).如果不需要使用掩码,可以传递一个空的Mat对象。掩码用于指定在哪些区域中进行最小值和最大值的查找。meanStdDev是OpenCV中的函数。用于计算给定图像的均值和标准差。

14.图像几何形状绘制

这节学习在图像上绘制或者填充矩形、圆形、椭圆形、线条等,下面是代码演示:

void QuickDemo::darwing_demo(Mat& image) {Rect rect;rect.x = 250;rect.y = 150;rect.width = 100;rect.height = 100;Mat bg = Mat::zeros(image.size(), image.type());rectangle(image, rect, Scalar(0, 0, 255), 2, 8, 0);circle(image, Point(350, 400), 50, Scalar(255, 0, 0), -1, 8, 0);line(image, Point(100, 100), Point(200, 200), Scalar(255, 255, 0));RotatedRect rrt;rrt.center = Point(200, 200);rrt.size = Size(100, 200);rrt.angle = 90;ellipse(image, rrt, Scalar(0, 0, 255), 2, 8);imshow("矩形", image);
}

代码解释:Rect是OpenCV库中定义的一个类,用于表示矩形区域。Rect rect这行代码声明了一个Rect类型的变量。rectangle() 函数用于在图像上绘制一个矩形。2: 表示矩形边框的厚度,如果设置为负数矩形会被填充。8表示8连接线,0表示旋转角度。circle() 函数用于在图像上绘制一个圆形。Point(100, 100): 这是Point类型的对象,表示圆心的位置。50:表示圆的半径。line() 函数用于在图像上绘制一条直线。 RotatedRect是OpenCV库中的函数用来绘制椭圆。

15.随机数与随机颜色

这节学习如何生成随机数并且根据随机种子在创建的图像中画随机线条。下面是代码演示:

void QuickDemo::random_drawing() {Mat canvas = Mat::zeros(Size(512, 512), CV_8UC3);int w = canvas.cols;int h = canvas.rows;RNG rng(12345);while (true) {int c = waitKey(100);if (c == 27) {break;}int x1 = rng.uniform(0, w);int y1 = rng.uniform(0, h);int x2 = rng.uniform(0, w);int y2 = rng.uniform(0, h);int b = rng.uniform(0, 255);int g = rng.uniform(0, 255);int r = rng.uniform(0, 255);//canvas = Scalar(0, 0, 0);line(canvas, Point(x1, y1), Point(x2, y2), Scalar(b, g, r), 1, LINE_AA, 0);imshow("随机绘制演示", canvas);}
}

代码解释:RNG rng(12345):这行代码创建了一个随机数生成器对象rng,并使用12345作为随机种子,随机种子是一个初始值,用于初始化随机数生成器的状态,相同的种子会产生相同的随机数序列。随机种子的作用是确保每次运行程序时,如果使用相同的种子,生成的随机数序列是相同的。可以确保每次运行程序时生成的随机数序列是可重复的。

16.多边形填充与绘制

这节学习如何绘制多边形:下面是代码是演示:

void QuickDemo::polyline_drawing_demo() {Mat canvas = Mat::zeros(Size(512, 512), CV_8UC3);Point p1(100, 100);Point p2(350, 100);Point p3(450, 280);Point p4(320, 450);Point p5(80, 400);std::vector<Point>pts;pts.push_back(p1);pts.push_back(p2);pts.push_back(p3);pts.push_back(p4);pts.push_back(p5);//polylines(canvas, pts, true, Scalar(0, 0, 255), 8, 8, 0);//fillPoly(canvas, pts, Scalar(255, 255, 0), LINE_AA, 0);std::vector<std::vector<Point>>contours;contours.push_back(pts);drawContours(canvas, contours, -1, Scalar(255, 0, 0), -1);imshow("多边形绘制", canvas);
}

代码解释:pts.push_back()将五个点添加到向量pts中,polyline() 函数是OpenCV中用于绘制多边形的函数,canvas:绘制画布。pts:点向量;true:表示多边形是闭合的。fillPoly函数用于填充多边形。drawContours()函数是OpenCV库中绘制轮廓的函数。

17.鼠标操作与响应

这节学习通过鼠标绘制矩形提取图像中的感兴趣区域(ROI),下面是代码演示:

Point sp(-1,-1);
Point ep(-1,-1);
Mat temp;
static void on_draw(int event, int x, int y, int flags, void* userdata){Mat image = *((Mat*)userdata);if(event == EVENT_LBUTTONDOWN){sp.x = x;sp.y = y;std::cout<<"start point:"<<sp<<sts::endl;}else if(event == EVENT_LBUTTONUP){ep.x = x;ep.y = y;int dx = ep.x - ep.x;int dy = ep.y - ep.y;if(dx>0 && dy>0){Rect box(sp.x, sp.y, dx, dy);rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);imshow("鼠标绘制", image);imshow("ROI区域", image(box));}else if(event ==EVENT_MOUSEMOVE){if(sp.x>0 && sp.y>0){ep.x = x;ep.y = y;int dx = ep.x - ep.x;int dy = ep.y - ep.y;if(dx>0 && dy>0){Rect box(sp.x, sp.y, dx, dy);temp.copyTo(image);rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);inshow("鼠标绘制", image);}}}
}
void QuickDemo::mouse_drawing_demo(Mat& image){nameWindow("鼠标绘制", WINDOW_AUTOSIZE);setMouseCallback("鼠标绘制”, on_draw, (void*)(&image));imshow("鼠标绘制", image);temp = image.clone();
}

代码解释:static void on_draw(int event, int x, int y, int flags, void* userdata){ 这行代码定义了一个名为 on_draw的静态函数,该函数在绘制事件发生时被调用。函数的参数包括:event: 表示绘制事件类型。当用户点击鼠标左键时,事件类型为EVENT_LBUTTONDOWN。flags:表示事件标志,userdata: 表示用户数据,在这个例子中是一个指向Mat类型的指针,表示要绘制图像。void QuickDemo::mouse_drawing_demo(Mat& image){ 这行代码定义了一个名为mouse_drawing_demo的函数,该函数用于实现鼠标绘制的功能,setMouseCallback()函数是OpenCV库中的函数,为鼠绘制窗口设置鼠标回调函数。on_draw是回调函数的名称,(void*)(&image)是将图像地址传递给回调函数,以便在回调函数中使用。

18.图像像素类型转换与归一化

这节学习将图像通整数类型转换为浮点数数类型并进行显示

void QuickDemo::norm_demo(Mat& image) {Mat dst;std::cout << image.type() << std::endl;image.convertTo(image, CV_32F);std::cout << dst.type() << std::endl;   // CV_8UC3, CV_32FC3normalize(image, dst, 1.0, 0, NORM_MINMAX);std::cout << dst.type() << std::endl;imshow("图像数据归一化", dst);
}

代码解释:这段代码首先将图像通过.convertTo()函数转换为32位浮点数类型。然后通过 归一化函数normlize()将图像归一化并存储在dst中,归一化的方式是NORM_MINMAX,最后显示归一化后的图像数据dst。

19.图像缩放与插值

这节学习图像的缩放和插值操作,内容相对简单。下面是代码演示:

void QuickDemo::resize_demo(Mat& image) {Mat zoomin, zoomout;int h = image.rows;int w = image.cols;resize(image, zoomin, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);imshow("zoomin", zoomin);resize(image, zoomout, Size(w * 1.5, h * 1.5), 0, 0, INTER_LINEAR);imshow("zoomout", zoomout);
}

代码解释:这里记住resize()函数就可以实现对图像的缩放。

20.图像翻转

这节学习图像的翻转,只需要使用flip() 函数改变其参数就能实现图像的水平翻转、上下翻转、对角线翻转。代码相对简单。

void QuickDemo::flip_demo(Mat& image) {Mat dst;//flip(image, dst, 0);//上下翻转//flip(image, dst, 1);//左右翻转flip(image, dst, -1); //对角线翻转,180°旋转imshow("图像翻转", dst);
}

21.图像旋转

这节学习对图像实现旋转,图像旋转是通过仿射变换实现的,图像旋转后的高宽会发生变换,新的高宽通过下面图示方式进行计算;
在这里插入图片描述
下面是代码演示:

void QuickDemo::rotate_demo(Mat& image) {Mat dst, M;int w = image.cols;int h = image.rows;M = getRotationMatrix2D(Point2f(w / 2, h / 2), 45, 1.0);double cos = abs(M.at<double>(0, 0));double sin = abs(M.at<double>(0, 1));int nw = cos * w + sin * h;int nh = sin * w + cos * h;M.at<double>(0, 2) = M.at<double>(0, 2) + (nw / 2 - w / 2);M.at<double>(1, 2) = M.at<double>(1, 2) + (nh / 2 - h / 2);warpAffine(image, dst, M, Size(nw, nh), INTER_LINEAR, 0, Scalar(255, 255, 255));imshow("旋转演示", dst);
}

代码解释:getRotationMatrix2D()是OpenCV库中的函数,它的功能是生成一个二维的旋转矩阵,Point2f类型的对象,表示旋转的中心点,45表示旋转的角度,1.0表示旋转的缩放因子。M是旋转矩阵,从旋转矩阵中取出cos seta 和 sin seta,接下来重新计算高宽,然后将旋转中心值重新赋值给M矩阵,组后通过warpAffine()函数进行旋转放射变换。

22.视频文件摄像头使用

这节学习如何调用电脑摄像头实时显示摄像头内容以及如何读取视频文件,对读取到的视频内容也可以使用图像处理中的一些处理进行显示,例如灰度变换、HSV、抠图等。下面是代码演示:

void QuickDemo::video_demo(Mat& image) {VideoCapture capture(0);  // 读出视频文件capture(../video/xxx.mp4)Mat frame;while (true) {capture.read(frame);flip(frame, frame, 1);if (frame.empty()) {break;}imshow("视频", frame);//TODO: do something...int c = waitKey(10);if (c == 27) {break;}}//releasecapture.release();
}

代码解释:VideoCapture是OpenCV中用于视频捕获的类,capture(0)表示从默认摄像头捕获视频,Mat是OpenCV中用于存储图像数据的类。frame是一个Mat对象,用于存储图像数据的类。

23.视频处理与保存

这节学习如何获取读取到的视频属性,例如高宽、视频帧总数、每秒的帧数(FPS)等。最后学习如何保存视频。下面是代码演示:

void QuickDemo::video_demo(Mat& image) {VideoCapture capture("C:/code/workspace/data/video/dance.mp4");  // 读出视频文件capture(../video/xxx.mp4) 读取摄像头capture(0)int frame_width = capture.get(CAP_PROP_FRAME_WIDTH);int frame_height = capture.get(CAP_PROP_FRAME_HEIGHT);int count = capture.get(CAP_PROP_FRAME_COUNT);double fps = capture.get(CAP_PROP_FPS);std::cout << "fram width:" << frame_width << std::endl;std::cout << "fram height:" << frame_height << std::endl;std::cout << "Number of frames:" << count << std::endl;std::cout << "FPS:" <<fps<< std::endl;VideoWriter writer("C:/code/workspace/data/video/test.mp4", capture.get(CAP_PROP_FOURCC), fps, Size(frame_width, frame_height), true);Mat frame;while (true) {capture.read(frame);flip(frame, frame, 1);if (frame.empty()) {break;}namedWindow("视频", WINDOW_NORMAL);resizeWindow("视频", 800, 600);imshow("视频", frame);//colorSpace_Demo(frame);writer.write(frame);//TODO: do something...int c = waitKey(10);if (c == 27) {break;}}//releasecapture.release();writer.release();
}

代码解释:获取视频的高宽等属性用capture.get()函数,保存视频文件时,VideoWriter是OpenCV库中用于视频写入的类。writer是VideoWriter对象的名称。capture是一个VideoCapture对象,用于视频捕获。write 是 VideoWriter 类的一个成员函数,用于将一帧图像写入到视频文件中。

24.图像直方图

这节学习绘制rgb图像三个通道的直方图曲线,并将它们可视化在一张画布上,下面是代码演示:

void QuickDemo::Histogram_demo(Mat& image){//三通道分离std::vector<Mat> bgr_plane;split(image, bgr_plane);//定义参数变量const int channels[1] = {0};const int bins[1] = {256};float hranges[2] = {0, 255};const float* ranges[1] = {hranges};Mat b_hist;Mat g_hist;Mat r_hist;//计算Blue、Green、Red通道的直方图calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);//显示直方图int hist_w = 512;int hist_h = 400;int bin_w = cvRound((double)hist_w / bins[0]);   //每个bin的宽度Mat histImage = Mat::zeros(histImage.rows, NORM_MINMAX, -1, Mat());Mat histImage = Mat::zeros(histImage.rows, NORM_MINMAX, -1, Mat());Mat histImage = Mat::zeros(histImage.rows, NORM_MINMAX, -1, Mat());//绘制直方图曲线for (int i = 1; i<bins[0];i++){line(histImage, Point(bin_w*(i-1), hist_h-cvRound(b_hist.at<float>(i-1))),Point(bin_w*(i), hist_h -cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);line(histImage, Point(bin_w*(i-1), hist_h-cvRound(g_hist.at<float>(i-1))),Point(bin_w*(i), hist_h -cvRound(g_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);line(histImage, Point(bin_w*(i-1), hist_h-cvRound(r_hist.at<float>(i-1))),Point(bin_w*(i), hist_h -cvRound(r_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);//显示直方图nameWindow("Histogram Demo", WINDOW_AUTOSIZE);imshow("Histogram Demo", histImage);

代码解释:cvRound是OpenCV中的一个函数,用于将浮点数四舍五入为最接近的整数。这里关于线段的起始坐标还没有搞清楚,

25.二维直方图

代码演示:

void QuickDemo::Histogram_2d_demo(Mat& image) {//2D直方图Mat hsv, hs_hist;cvtColor(image, hsv, COLOR_BGR2HSV);   // H取值范围是(0,180),S和V的取值范围是(0,255)int hbins = 30, sbins = 32;int hist_bins[] = { hbins, sbins };float h_range[] = { 0, 180 };float s_range[] = { 0, 256 };const float* hs_ranges[] = { h_range, s_range };int hs_channels[] = { 0,1 };calcHist(&hsv, 1, hs_channels, Mat(), hs_hist, 2, hist_bins, hs_ranges, true, false);double maxVal = 0;minMaxLoc(hs_hist, 0, &maxVal, 0, 0);int scale = 10;Mat hist2d_image = Mat::zeros(sbins * scale, hbins * scale, CV_8UC3);for (int h = 0; h < hbins; h++) {for (int s = 0; s < sbins; s++) {float binVal = hs_hist.at<float>(h, s);int intensity = cvRound(binVal * 255 / maxVal);rectangle(hist2d_image, Point(h * scale, s * scale),Point((h + 1) * scale - 1, (s + 1) * scale - 1), Scalar::all(intensity), -1);}}applyColorMap(hist2d_image, hist2d_image,COLORMAP_JET);imshow("H-S Histogram", hist2d_image);imwrite("C:/code/workplace/data/image/his2d.jpg", hist2d_image);
}

26.直方图均衡化

原理:统计直方图 》归一化直方图》累计直方图》区间转换
直方图均衡化只能对单通道灰度图像进行均衡化,如果对彩色图像进行均衡化可以将彩色图像转换到HSV色彩空间然后对V通道的亮度进行直方图均衡化。

void QuickDemo::histogram_eq_demo(Mat& image){Mat gray;cvtColor(image, gray, COLOR_BGR2GRAY);Mat dst;equalizeHist(gray, dst);imshow("直方图均衡化", dst);
}

27.图像卷积和高斯模糊

这节学习对图像进行卷积操作和使用高斯核函数对图像进行模糊处理,下面是代码演示:

void QuickDemo::blur_demo(Mat& image) {Mat dst;blur(image, dst, Size(13, 13), Point(-1, -1));imshow("图像模糊", dst);
}void QuickDemo::gaussian_blur_demo(Mat& image) {Mat dst;GaussianBlur(image, dst, Size(5, 5), 15);imshow("高斯模糊", dst);
}

代码解释:卷积操作使用blur() 函数,GaussianBlur() 是高斯模糊的函数。

28.双边模糊

双边模糊(Bilateral blur)是一种图像处理技术,旨在模糊图像中的细节,同时保留边缘的清晰度,从而防止边缘变得模糊或失真。与传统的高斯模糊不同,双边模糊考虑了像素之间的空间距离和像素值之间的差异。具体来说,它利用了一个像素值相似性函数,这个函数在像素之间的距离较大时减小,从而保留了边缘的锐利度。因此,双边模糊常用于需要在保留细节的同时进行图像平滑处理的应用场景,如图像去噪或者一些图像特效的实现中。

void QuickDemo::bifilter_demo(Mat& image) {Mat dst;bilateralFilter(image, dst, 0, 100, 10);imshow("双边模糊", dst);
}

29.人脸实时检测

这节通过加载训练好的模型文件调用摄像头 或者加载视频文件实现人脸的实时检测,在实现该项目时需要用到两个文件,这两个文件观看b站视频OpenCV与C++入门30讲相应的视频获得下载地址,下面是代码演示:

#include <opencv2/opencv.hpp>
#include <iostream>using namespace cv;int main(int argc, char** argv) {;std::string pf_file_path_path = "C:/code/workspace/model/opencv_face_detector_uint8.pb";std::string pbtxt_file_path = "C:/code/workspace/model/opencv_face_detector.pbtxt";cv::dnn::Net net = cv::dnn::readNetFromTensorflow(pf_file_path_path, pbtxt_file_path);VideoCapture cap(0);cv::Mat frame;while (true) {cap.read(frame);if (frame.empty()) {break;}cv::Mat blob = cv::dnn::blobFromImage(frame, 1.0, Size(300, 300), cv::Scalar(104, 177, 123), false, false);net.setInput(blob);cv::Mat probs = net.forward();// 1x1xNx7cv::Mat detectMat(probs.size[2], probs.size[3], CV_32F, probs.ptr<float>());for (int row = 0; row < detectMat.rows; row++) {float conf = detectMat.at<float>(row, 2);if (conf > 0.5) {float x1 = detectMat.at<float>(row, 3) * frame.cols;float y1 = detectMat.at<float>(row, 4) * frame.rows;float x2 = detectMat.at<float>(row, 5) * frame.cols;float y2 = detectMat.at<float>(row, 6) * frame.rows;cv::Rect box(x1, y1, x2 - x1, y2 - y1);cv::rectangle(frame, box, cv::Scalar(0, 0, 255), 2, 8);}}cv::imshow("OpenCV4.6DNN人脸检测演示", frame);char c = waitKey(1);if (c == 27) {break;}	}cv::waitKey(0);cv::destroyAllWindows();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/41613.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云存储的降本增效与运维

小浩负责公司存储架构层&#xff0c;需要确保存储层不会成为公司业务系统的性能瓶颈&#xff0c;让数据读写达到最佳性能。那么小浩可以从哪些方面着手优化性能呢&#xff1f;他继续求助系统架构师大雷。 小浩&#xff1a;雷哥&#xff0c;PD反馈公司系统最近响应很慢&#xff…

HTTP模块(一)

HTTP服务 本小节主要讲解HTTP服务如何创建服务&#xff0c;查看HTTP请求&响应报文&#xff0c;还有注意事项说明&#xff0c;另外讲解本地环境&Node环境&浏览器之间的链路图示&#xff0c;如何提取HTTP报文字符串&#xff0c;及报错信息查询。 创建HTTP服务端 c…

lspci

【原】Linux之PCIE三种空间解析 PCIe学习笔记——2.PCIe配置空间 PCIE学习&#xff08;2&#xff09;PCIE配置空间详解 开发者分享 | 使用 lspci 和 setpci 调试 PCIe 问题 b : 字节 w&#xff1a;word L&#xff1a; 4byte

LLM - 词表示和语言模型

一. 词的相似度表示 (1): 用一系列与该词相关的词来表示 (2): 把每个词表示一个独立的符号(one hot) (3): 利用该词上下文的词来表示该词 (3): 建立一个低维度的向量空间&#xff0c;用深度学习方法将该词映射到这个空间里(Word Embedding) 二&#xff1a;语言模型 (1): 根…

Redis源码整体结构

一 前言 Redis源码研究为什么先介绍整体结构呢?其实也很简单,作为程序员的,要想对一个项目有快速的认知,对项目整体目录结构有一个清晰认识,有助于我们更好的了解这个系统。 二 目录结构 Redis源码download到本地之后,对应结构如下: 从上面的截图可以看出,Redis源码一…

52-5 内网代理2 - LCX端口转发(不推荐使用LCX)

环境搭建: 本地开3台虚拟机:kali(必须)、windows2012与2008 (可换成其他windows虚拟机) kali - 网络配置成桥接模式 windows2012 - 设置两个网卡,NAT与桥接模式 注意:windows2012要关闭防火墙,要不然其他主机ping不通 关闭防火墙后再开启远程桌面连接 windwos20…

去O化神器 Exbase

随着去O化进程推动&#xff0c;很多旧业务依赖的oracle数据库&#xff0c;都需要实现做数据库的替换&#xff0c;当下能很好兼容Oracle&#xff0c;并实现异构数据库之间转换的工具并不多。这里给大家推荐一个商业工具数据库迁移工具exbase&#xff08;北京海量&#xff09;&am…

昇思MindSpore 25天学习打卡营|day18

DCGAN生成漫画头像 在下面的教程中&#xff0c;我们将通过示例代码说明DCGAN网络如何设置网络、优化器、如何计算损失函数以及如何初始化模型权重。在本教程中&#xff0c;使用的动漫头像数据集共有70,171张动漫头像图片&#xff0c;图片大小均为96*96。 GAN基础原理 这部分原…

想知道你的电脑能不能和如何升级RAM吗?这里有你想要的一些提示

考虑给你的电脑增加更多的RAM,但不确定从哪里开始?本指南涵盖了有关升级Windows PC或笔记本电脑中RAM的所有信息。 你需要升级RAM吗 在深入研究升级RAM的过程之前,评估是否需要升级是至关重要的。你是否经历过系统滞后、频繁的BSOD错误或应用程序和程序突然崩溃?这些症状…

从零开始的python学习生活

pycharm部分好用快捷键 变量名的定义 与之前学习过的语言有所不同的是&#xff0c;python中变量名的定义更加的简洁 such as 整形。浮点型和字符串的定义 money50 haha13.14 gaga"hello"字符串的定义依然是需要加上引号&#xff0c;也不需要写&#xff1b;了 字符…

【网站推荐】Developer Roadmaps 开发者学习路线

你是否想学习某门技术而苦苦找不到学习路线。本文推荐一个网站&#xff0c;解决学习路径问题。 roadmap.sh 旨在创建路线图、指南和其他教育内容&#xff0c;以帮助指导开发人员选择路径并指导他们的学习。 技术路线包括了前端后端安卓iosUI设计等内容&#xff0c;一些技术比如…

antdesignvue对话框用户可移动并缩放

原贴 轻松搞定Ant Design Modal对话框拖拽缩放 - ByteZoneX社区https://www.bytezonex.com/archives/IFRuoJhd.html Ant Design 模态对话框&#xff1a;实现拖拽缩放功能 **子 Ant Design 是一个流行的前端 UI 框架&#xff0c;提供了一系列实用的组件&#xff0c;包括模态对…

ESP32CAM物联网教学02

ESP32CAM物联网教学02 物联网门锁 小智来到姑姑家门口&#xff0c;按了门铃&#xff1b;还在公司上班的姑姑用电脑给小智开了门&#xff0c;让他先进屋休息。小智对物联网门锁产生了兴趣&#xff1a;什么是物联网&#xff1f;为什么这么厉害&#xff1f; 初识物联网 我们在百…

andboxie-Plus - 知名沙盒软件、支持游戏多开测试软件

我们经常会需要用到一些毒瘤软件——它们可能不是真正的恶意软件&#xff0c;但总爱偷摸干一些流氓行为。 工作中&#xff0c;有时还不得不安装使用一些来路不明、不能完全信任的可疑软件。 装上吧&#xff0c;心里膈应、难受&#xff1b;不装吧&#xff0c;有些工作又进行不…

【驱动篇】龙芯LS2K0300之PWM设备驱动

实验目的 利用脉冲调制效应&#xff08;PWM&#xff09;等效改变输出功率大小控制LED&#xff0c;从而实现呼吸灯效果&#xff0c;需要用到RGB LED模块 模块连接 IO 插针接口上一共集成了两路PWM&#xff0c;分别是PWM2和PWM3&#xff0c;对应GPIO88、GPIO89 PWM2和PWM3对…

期末考试结束,老师该如何私发成绩?

随着期末考试的落幕&#xff0c;校园里又恢复了往日的宁静。然而&#xff0c;对于老师们来说&#xff0c;这并不意味着工作的结束&#xff0c;相反&#xff0c;一系列繁琐的任务才刚刚开始。 成绩单的发放&#xff0c;就是其中一项让人头疼的工作。家长们焦急地等待着孩子的考试…

【Linux】在线求助命令--help,man page , info page

我们知道Linux有很多的命令&#xff0c;那LInux要不要背命令&#xff1f; 答案是背最常用的那些就行了 那有的时候我们想查询一些命令的详细用法该怎么办呢&#xff1f; 这里我给出3种方法 1.--help --help的使用方法很简单啊 要查询的命令 --help 我们看个例子 这里我只…

CRT工具

CRT工具 传输位置设置 打开SFTP alt p 命令 ls&#xff1a;远程机器当前目录内容 lls&#xff1a;传输位置文件的目录内容 pwd&#xff1a;远程机器的当前位置 lpwd&#xff1a;传输位置的位置 get 文件&#xff1a;ftp传输文件 get -r 文件夹&#xff1a;ftp传输文件…

大华DSS user_toLoginPage.action命令执行漏洞

免责声明 本文章仅做网络安全技术研究使用&#xff01;严禁用于非法犯罪行为&#xff0c;请严格遵守国家法律法规&#xff1b;请勿利用文章内的相关技术从事非法测试&#xff0c;如因此产生的一切不良后果与文章作者无关。使用本文所提供的信息或工具即视为同意本免责声明&…

go语言day11 错误 defer(),panic(),recover()

错误&#xff1a; 创建错误 1&#xff09;fmt包下提供的方法 fmt.Errorf(" 格式化字符串信息 " &#xff0c; 空接口类型对象 ) 2&#xff09;errors包下提供的方法 errors.New(" 字符串信息 ") 创建自定义错误 需要实现error接口&#xff0c;而error接口…