OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形绘制)

系列文章目录

  1. OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间)
  2. OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形绘制)【本文】
  3. OpenCV函数简记_第三章数字图像的滤波处理(方框,均值,高斯,中值和双边滤波)
  4. OpenCV函数简记_第四章数字图像的形态学处理和图像金字塔。(腐蚀、膨胀、开,闭运算、形态学梯度、顶帽和黑帽以及图像金字塔)


前言

本系列文章仅是本人学习OpenCV过程中,针对常用的OpenCV函数的相关参数和简单用法进行记录,方便随时查询和使用。对于具体算法原理不做过多介绍。
本文C++使用的OpenCV版本是的4.5.5。
python使用的OpenCV版本是4.5.3.56
官方文档参考的是 4.6.0-dev

需要查找具体函数可以直接从目录中查询。[]符号内为函数名称,包含C++和python两个语言的代码


本文内容:
本文描述了针对数字图像的读取和保存方式,图像像素值获取的方式,图像ROI获取,图像混合方法以及多种图形的绘制。以上都有python和C++两种代码示例。

参考资料:
1.OpenCV官方文档
2.C++迭代器(STL迭代器)iterator详解
3.毛星云-OpenCV3编程入门
4.博客:OpenCV学习笔记(五十六)——InputArray和OutputArray的那些事core, 作者yang_xian521
5.博客:OpenCV 创建图像时,CV_8UC1,CV_32FC3,CV_32S等参数的含义,作者:Young__Fan
6.Captain_zw的原创文章:【C/C++】size_t详解


1. 窗口的创建和删除

1.1 窗口的创建[namedWindow]

namedWindow()
作用:
创建一个用于显示图片的窗口,之后通过自定义的窗口名称进行引用,如果在调用该函数前已经存在同名的窗口,则该函数则不运行。
可以调用 destroyWindow或者destroyAllWindows函数来关闭之前占用内存的窗口。
对于一个简单程序而言,可以不用关闭,因为在程序结束后,会自动关闭之前所有的窗口。

函数形式:

C++:
void cv::namedWindow ( const String & winname, int flags = WINDOW_AUTOSIZE)
Python:
cv.namedWindow( winname, flags) -> None

参数解释(以C++展示的参数为例):
1.const String& winname: 窗口的名称
2.int flags = WINDOW_AUTOSIZE: 窗口显示类型。默认为WINDOW_AUTOSIZE

flags类型 解释
WINDOW_NORMAL WINDOW_NORMAL 可以使用户改变窗口大小,将展示图像的尺寸缩放到和窗口大小一致。
WINDOW_AUTOSIZE WINDOW_AUTOSIZE 会自动的将窗口尺寸调整至和展示图像一致,用户不可以手动修改窗口的尺寸。
WINDOW_FREERATIO WINDOW_FREERATIO 调整图像,不考虑图像比例。
WINDOW_KEEPRATIO WINDOW_KEEPRATIO调整图像,同时依然保存图像比例
WINDOW_GUI_NORMAL WINDOW_GUI_NORMAL是旧的创建窗口方法,其创建的窗口没有状态栏和工具栏。
WINDOW_GUI_EXPANDED WINDOW_GUI_EXPANDED是一个新的增强的GUI。默认情况下: flags == WINDOW_AUTOSIZE| WINDOW_KEEPRATIO| WINDOW_GUI_EXPANDED

代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	namedWindow("WINDOW_NORMAL", WINDOW_NORMAL);
	imshow("WINDOW_NORMAL", img);

	namedWindow("WINDOW_AUTOSIZE", WINDOW_AUTOSIZE);
	imshow("WINDOW_AUTOSIZE", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("cat.jpg")
    cv2.namedWindow("WINDOW_NORMAL", cv2.WINDOW_NORMAL)
    cv2.imshow("WINDOW_NORMAL", img)

    cv2.namedWindow("WINDOW_AUTOSIZE", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("WINDOW_AUTOSIZE", img)
    cv2.waitKey(0)
if __name__ == "__main__":
    main()

在这里插入图片描述

1.2 窗口的删除[destroyAllWindows / destroyWindow]

destroyAllWindows()
作用:
关闭所有的HighGUI窗口。
函数形式:

C++:
void cv::destroyAllWindows()
Python:
cv.destroyAllWindows() -> None

代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	namedWindow("WINDOW_NORMAL", WINDOW_NORMAL);
	imshow("WINDOW_NORMAL", img);
	destroyAllWindows();//关闭所有窗口。
	return 0;
}

destroyWindow()
作用:
关闭指定的窗口
函数形式:

C++:
void cv::destroyWindow(const String & winname)
Python:
cv.destroyWindow(winname) ->None

参数解释(以C++展示的参数为例):
1.const String & winname:窗口的名称。
代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	namedWindow("WINDOW_NORMAL", WINDOW_NORMAL);
	imshow("WINDOW_NORMAL", img);
	destroyWindow("WINDOW_NORMAL");//关闭指定的WINDOW_NORMAL窗口
	return 0;
}
# PYTHON
import cv2
def main():
    img = cv2.imread("cat.jpg")
    cv2.namedWindow("WINDOW_NORMAL", cv2.WINDOW_NORMAL)
    cv2.imshow("WINDOW_NORMAL", img)
    cv2.destroyWindow("WINDOW_NORMAL")# 关闭指定的WINDOW_NORMAL窗口
if __name__ == "__main__":
    main()

1.3 等待函数[waitKey]

waitKey()
作用:
等待按下的键。
delay<=0时,函数无限时等待一个按键按下,并返回按键的ASCII码。
delay>0时,等待delay个毫秒。如果中途按下按键,则返回按键的ASCII码,如果截止到delay个毫秒完,仍没有按键按下则返回-1。

备注1: 由于操作系统在切换线程之间有一个最短的时间,该函数不会等待准确的delay个毫秒,而是等待至少delay个毫秒。要检查一个键按下但不等待它,使用pollKey函数(pollKey函数具体参考官方技术文档)。

函数形式:

C++:
int cv::waitKey (int delay = 0)
Python:
cv.waitKey(delay) ->retval

参数解释(以C++展示的参数为例):
1.delay: int类型,表示延迟的单位数量,单位为毫秒。<=0是特殊值,表示无限等待。
代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	
	Mat img = imread("cat.jpg");
	namedWindow("WINDOW_NORMAL", WINDOW_NORMAL);
	imshow("WINDOW_NORMAL", img);
	int iOn;
	iOn = waitKey(0);//按下A键
	printf("%d", iOn);
	return 0;
}

在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("cat.jpg")
    cv2.namedWindow("WINDOW_NORMAL", cv2.WINDOW_NORMAL)
    cv2.imshow("WINDOW_NORMAL", img)
    iOn = cv2.waitKey(0) #按下A键
    print(iOn)
    cv2.destroyWindow("WINDOW_NORMAL")
    
if __name__ == "__main__":
    main()

在这里插入图片描述

2. 图像的读写与显示

注意:本文下面函数中提到的InputArray类型和OutputArray类型,都可以是Mat、Mat_、Mat_<T, m, n>、vector、vector<vector>、vector。OutputArray是InputArray的派生类。

2.1 图像的读入[imread]

imread()
作用:
通过指定的读图方式来读取图像,一般读图方式默认为IMREAD_COLOR。如果图像无法读取(因为缺少文件、权限不当、不支持或无效的格式),该函数将返回一个空矩阵(Mat::data==NULL)。

目前支持的文件格式如下:

  • Windows位图:.bmp,.dib
  • JPEG文件:.jpeg,.jpg,*.jpe
  • JPEG 2000文件:*.jp2
  • Portable Network Graphics(便携式网络图形):*.png
  • WebP:*.webp
  • Portable image format(便携式图像格式):*.pbm, *.pgm, *.ppm, *.pxm, *.pnm
  • PFM文件:*.pfm
  • Sun rasters:*.sr, *.ras
  • TIFF文件:*.tiff, *.tif
  • OpenEXR镜像文件: *.exr
  • Radiance HDR :*.hdr, *.pic
  • GDAL支持的栅格和矢量地理空间数据

注意事项

  • 该函数根据内容决定图像的类型,而不是文件扩展名。
  • 对于彩色图像,解码后的图像将按B G R顺序存储。
  • 当使用IMREAD_GRAYSCALE时,如果可用,将使用编解码器的内部灰度转换。结果可能与cvtColor()的输出不同。
  • 如果图像文件中嵌入了EXIF信息,则会考虑EXIF方向,因此图像将相应地旋转,除非传入了标志IMREAD_IGNORE_ORIENTATION或IMREAD_UNCHANGED。
  • 使用IMREAD_UNCHANGED标志保留PFM映像中的浮点值。默认情况下,像素数必须小于2^30。

函数形式:

C++:
Mat cv::imread(const String & filename, int flags = IMREAD_COLOR )
Python:
cv.imread(filename, flags) ->retval

参数解释(以C++展示的参数为例):
1.const String & filename:文件名称(可包含路径)
2. int flags:读取图像的方式,默认为IMREAD_COLOR。也可以使用-1,0,1:
1——cv2.IMREAD_COLOR:加载BGR图像。这是默认标志。
0——cv2.IMREAD_GRAYSCALE:以灰度模式加载图像
-1——cv2.IMREAD_UNCHANGED:加载BGRA图像,包括alpha通道

全部的flag类型如下表所示:

flag 解释
IMREAD_UNCHANGED 返回加载的图像是BGRA。
IMREAD_GRAYSCALE 读取为单波段灰度图像。
IMREAD_COLOR 读取为3波段BGR图像
IMREAD_ANYDEPTH 当输入有相应深度时返回16位/32位图像,否则转换为8位图像。
IMREAD_ANYCOLOR 图像将以任何可能的颜色格式读取。
IMREAD_LOAD_GDAL 使用gdal驱动程序加载图像。
IMREAD_REDUCED_GRAYSCALE_2 如果设置,始终将图像转换为单通道灰度图像,图像尺寸减小1/2。
IMREAD_REDUCED_COLOR_2 如果设置,始终将图像转换为3通道BGR图像,图像尺寸减小1/2。
IMREAD_REDUCED_GRAYSCALE_4 如果设置,始终将图像转换为单通道灰度图像,图像尺寸减小1/4。
IMREAD_REDUCED_COLOR_4 如果设置,始终将图像转换为3通道BGR图像,图像尺寸减小1/4。
IMREAD_REDUCED_GRAYSCALE_8 如果设置,始终将图像转换为单通道灰度图像,图像尺寸减小1/8。
IMREAD_REDUCED_COLOR_8 如果设置,始终将图像转换为3通道BGR图像,图像尺寸减小1/8。
IMREAD_IGNORE_ORIENTATION 如果设置,不要根据EXIF的方向标志旋转图像。

代码示例:

//C++
#include<opencv2/opencv.hpp>

using namespace cv;

int main() {
    
    
	Mat img = imread("cat.jpg", 1);
	namedWindow("BGR");
	imshow("BGR", img);

	img = imread("cat.jpg", 0);
	namedWindow("GRAY");
	imshow("GRAY", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述
python运行结果同C++一致

# PYTHON
import cv2
def main():
    img = cv2.imread("cat.jpg", 1)
    cv2.namedWindow("BGR")
    cv2.imshow("BGR", img)

    img = cv2.imread("cat.jpg", 0)
    cv2.namedWindow("GRAY")
    cv2.imshow("GRAY", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
if __name__ == "__main__":
    main()

2.2 图像的展示[imshow]

imshow()
作用:
在指定的窗口中显示图像。如果在此函数之前没有创建窗口,则假定创建一个带有cv::WINDOW_AUTOSIZE的窗口。

imshow功能用于在指定窗口中显示图像。如果窗口是用cv::WINDOW_AUTOSIZE标志创建的,图像会显示其原始大小,但是它仍然受到屏幕分辨率的限制。否则,图像将缩放以适应窗口。该函数可以根据深度对图像进行缩放:

  • 如果图像是8位无符号的,则显示为原样。
  • 如果图像是16位无符号的,则像素被除以256。即将[0,255*256]的取值范围映射为[0,255]。
  • 如果图像是32位或64位浮点,则像素值乘以255。即,将取值范围[0,1]映射为[0,255]。
  • 32位整数图像不再处理,由于需要的变换的模糊性。使用特定于图像上下文的自定义预处理转换为8位无符号矩阵。
    注意:显示图像场合 waitkey()配合使用,否则无法长时间显示

函数形式:

C++:
void cv::imshow(const String & winname, InputArray mat )
Python:
cv.imshow( winname, mat ) -> None

参数解释(以C++展示的参数为例):

1.const String & winname: 窗口名字
2. InputArray mat:图像数据。

代码示例:
见上例

2.3 图像的写入[imwrite]

imwrite()
作用:
将输入的数据按照指定路径、名字和保存方式进行保存。

函数形式:

C++:
bool cv::imwrite(const String & filename, InputArray img, const std::vector< int > & params = std::vector< int >() )
Python:
cv.imwrite(filename, img[, params]) -> retval

参数解释(以C++展示的参数为例):
1.const String & filename:保存图像的名称,带后缀。
2.InputArray img: 图像数据。
3 const std::vector< int > & params :表示为特定格式保存的参数编码,成对存在(paramId_1, paramValue_1, paramId_2, paramValue_2, … .)参见cv::ImwriteFlags。不做设置时,默认值为vector< int >(),一般情况不需要填写。

以下是imwriteFlags的参数表,机翻。如果不清楚参考cv::ImwriteFlags

Imwriteflags 标志 内容
IMWRITE_JPEG_QUALITY 对于JPEG,质量可以从0到100(越高越好)。默认值为95。
IMWRITE_JPEG_PROGRESSIVE 启用JPEG特性,0或1,默认为False。
IMWRITE_JPEG_OPTIMIZE 启用JPEG特性,0或1,默认为False。
IMWRITE_JPEG_RST_INTERVAL JPEG重启间隔,0—65535,默认为0—不重启。
IMWRITE_JPEG_LUMA_QUALITY 单独亮度质量等级,0 - 100,默认为-1 -不使用。
IMWRITE_JPEG_CHROMA_QUALITY 分离色度质量等级,0 - 100,默认为-1 -不使用。
IMWRITE_JPEG_SAMPLING_FACTOR 对于JPEG,设置采样因子。
IMWRITE_PNG_COMPRESSION 对于PNG,它可以是0到9的压缩级别。值越高,压缩时间越长,文件大小越小。如果指定了,策略将被更改为IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY)。默认值为1(最佳速度设置)。
IMWRITE_PNG_STRATEGY cv::ImwritePNGFlags之一,默认为IMWRITE_PNG_STRATEGY_RLE。
IMWRITE_PNG_BILEVEL 二进制png图,0或者1,默认为0
IMWRITE_PXM_BINARY 对于PPM、PGM或PBM,它可以是二进制格式标志,0或1。默认值为1。
IMWRITE_EXR_TYPE
IMWRITE_EXR_COMPRESSION EXR存储类型(默认为FLOAT (FP32))
IMWRITE_WEBP_QUALITY 覆盖EXR压缩类型(ZIP_COMPRESSION = 3是默认值)对于WEBP,质量可以从1到100(越高越好)。默认情况下(没有任何参数),对于超过100的质量使用无损压缩。
IMWRITE_PAM_TUPLETYPE 对于PAM,将TUPLETYPE字段设置为为该格式定义的相应字符串值。
IMWRITE_TIFF_RESUNIT 对于TIFF,用来指定要设置哪个DPI分辨率单元;有关有效值,请参阅libtiff文档。
IMWRITE_TIFF_XDPI 对于TIFF,使用来指定X方向DPI。DPI的概念可参考上章OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间)解释
IMWRITE_TIFF_YDPI 对于TIFF,使用来指定Y方向DPI。
IMWRITE_TIFF_COMPRESSION 对于TIFF,使用来指定图像压缩方案。有关与压缩格式对应的整型常量,请参阅libtiff。注意,对于深度为CV_32F的图像,只使用libtiff的SGILOG压缩方案。对于其他支持深度,压缩方案可以由该标志指定;LZW压缩是默认值。
IMWRITE_JPEG2000_COMPRESSION_X1000 对于JPEG2000,使用来指定目标压缩率(乘以1000)。取值为0 ~ 1000。默认是1000。

代码示例:

enum ImwriteFlags {
IMWRITE_JPEG_QUALITY = 1,
IMWRITE_JPEG_PROGRESSIVE = 2,
IMWRITE_JPEG_OPTIMIZE = 3,
IMWRITE_JPEG_RST_INTERVAL = 4,
IMWRITE_JPEG_LUMA_QUALITY = 5,
IMWRITE_JPEG_CHROMA_QUALITY = 6,
IMWRITE_PNG_COMPRESSION = 16
IMWRITE_PNG_STRATEGY = 17,
IMWRITE_PNG_BILEVEL = 18,0.
IMWRITE_PXM_BINARY = 32,
IMWRITE_EXR_TYPE = (3 << 4) + 0,
IMWRITE_EXR_COMPRESSION = (3 << 4) + 1,
IMWRITE_WEBP_QUALITY = 64,
IMWRITE_PAM_TUPLETYPE = 128,
IMWRITE_TIFF_RESUNIT = 256,
IMWRITE_TIFF_XDPI = 257,
IMWRITE_TIFF_YDPI = 258,
IMWRITE_TIFF_COMPRESSION = 259,
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272 };

//C++
//以IMWRITE_PNG_COMPRESSION 为例。 IMWRITE_PNG_COMPRESSION 的对应值为16
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;

int main() {
    
    
	Mat img = imread("cat.jpg", 1);
	vector<int>flag1 = {
    
    16, 0};
	vector<int>flag2 = {
    
    16, 9};

	imwrite("cat.png", img);
	imwrite("cat1.png", img, flag1);
	imwrite("cat2.png", img, flag2);
	return 0;
}

在这里插入图片描述
python结果同上

# PYTHON
import cv2
def main():
    img = cv2.imread("cat.jpg")
    cv2.imwrite("cat.png", img)
    cv2.imwrite("cat1.png", img, (16, 0))
    cv2.imwrite("cat2.png", img, (16, 9))
if __name__ == "__main__":
    main()

3. 图像像素值的获取和修改

3.1 C++获取Mat类型图像像素值并修改

补充知识:
1.Mat类由两部分组成,分别是矩阵头(包含矩阵的尺寸、存储方法、存储地址等信息)和一个指向存储所有像素的矩阵的指针。
2.通常Mat类的复制,例如拷贝构造函数,都仅是复制矩阵头,不复制矩阵。如果要复制矩阵数据则需要函数clone()和函数copyTo()。具体方法本处不做过多解释。
3.值得注意的是Mat数据存储的方式是依次存储每个位置上所有通道像素数后再存储下一个。因此它的尺度为(rows × \times ×(col*channels))

3.1.1 指针访问[ptr]

ptr是Mat的成员函数,可以获取指定行数的数据首地址。

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	Mat changeImg;
	//复制矩阵数据
	img.copyTo(changeImg);

	for (int row = 0; row < changeImg.rows; row++) {
    
    
		// ptr函数可以得到任意行的首地址
		uchar* data = changeImg.ptr<uchar>(row);
		// mat类型存储数据的形式是row * (col*channls)
		for (int col = 0; col < changeImg.cols * changeImg.channels(); col++) {
    
    
			//修改像素值,降低对比度
			data[col] = data[col] / 2;
		}	
	}
	namedWindow("BGR", WINDOW_AUTOSIZE);
	namedWindow("BGR_change", WINDOW_AUTOSIZE);
	imshow("BGR", img);
	imshow("BGR_change", changeImg);
	waitKey(0);
	return 0;
}

在这里插入图片描述

3.1.2 迭代器[iterator]

STL迭代器 iterator

定义:容器类名::iterator 迭代器名

要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。

通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。

迭代器都可以进行++操作。反向迭代器和正向迭代器的区别在于:

  • 对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;
  • 而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。

更多细节请参考C++迭代器(STL迭代器)iterator详解

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	Mat changeImg;
	img.copyTo(changeImg);
	//定义迭代器, Vec3b表示Vec<uchar, 3>
	Mat_<Vec3b>::iterator it = changeImg.begin<Vec3b>();//开始位置
	Mat_<Vec3b>::iterator itend = changeImg.end<Vec3b>();//结束位置
	for (;it!=itend; it++) {
    
    
		//降低对比度
		(*it)[0] = (*it)[0]/2;
		(*it)[1] = (*it)[1]/2;
		(*it)[2] = (*it)[2]/2;
	}
	namedWindow("BGR", WINDOW_AUTOSIZE);
	namedWindow("BGR_change", WINDOW_AUTOSIZE);
	imshow("BGR", img);
	imshow("BGR_change", changeImg);
	waitKey(0);
	return 0;
}

3.1.3 动态地址计算[at]

Mat at()
作用
template< typename _Tp> inline
Tp& Mat::at(int i0, int i1)
参数形式
1.int i0:Mat类型的行。
2.int i1:Mat类型的列。
3. return Tp类型的数据。
类型有:

类型
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;

代码【结果同上】

//C++
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	Mat changeImg;
	img.copyTo(changeImg);

	for (int row = 0; row < changeImg.rows; row++) {
    
    
		for (int col = 0; col < changeImg.cols; col++) {
    
    
			//修改像素值,降低对比度
			changeImg.at<Vec3b>(row, col)[0] = changeImg.at<Vec3b>(row, col)[0] / 2;
			changeImg.at<Vec3b>(row, col)[1] = changeImg.at<Vec3b>(row, col)[1] / 2;
			changeImg.at<Vec3b>(row, col)[2] = changeImg.at<Vec3b>(row, col)[2] / 2;
		}	
	}
	namedWindow("BGR", WINDOW_AUTOSIZE);
	namedWindow("BGR_change", WINDOW_AUTOSIZE);
	imshow("BGR", img);
	imshow("BGR_change", changeImg);
	waitKey(0);
	return 0;
}

3.2 python获取numpy类型图像像素值并修改

# PYTHON
import cv2
import copy
def main():
    img = cv2.imread("cat.jpg")
    rows = img.shape[0]
    cols = img.shape[1]
    img2 = copy.deepcopy(img)
    for row in range(rows):
        for col in range(cols):
            img2[row, col] = img2[row, col] + 20
    #或者直接相加 img2 = img2 + 20
    cv2.namedWindow("changeImg", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("changeImg", img2)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

注意:python中使用img2 = img2 + 20可以对所有通道的像素增加20,而Mat类型的图像数据如果进行img2 = img2 + 20则只对第一个通道(Blue)增加20

4 图像特征的获取(行,列,通道数,数据类型,像素的数目)

# PYTHON
import cv2
import copy

def main():
    img = cv2.imread("cat.jpg")
    # shape是tuple类型的数据
    # 其中shape[0]是行数,shape[1]是列数,shape[2]是通道数
    print(img.shape)
    #获取像素的数目,shape[0]*shape[1]*shape[2]
    print(img.size)
    #获取图像数据类型
    print(img.dtype)

if __name__ == "__main__":
    main()

在这里插入图片描述

C++版本

//C++
#include<opencv2/opencv.hpp>
#include<iostream>
#include<typeinfo>
using namespace std;
using namespace cv;

int main() {
    
    

	Mat img = imread("cat.jpg");
	cout << "图像行数" << img.rows << endl;
	cout << "图像列数" << img.cols << endl;
	cout << "图像通道数" << img.channels() << endl;
	cout << "图像像素的数目" << img.size[0]* img.size[1] * img.channels()<< endl;
	cout << "图像的类型" << typeid(img).name() << endl;
	cout << "图像像素的类型" << typeid(img.data).name() << endl;
	return 0;
}

在这里插入图片描述

5 图像感兴趣区域的获取(Rect/Range)

5.1 Rect获取感兴趣区域[Rect]

Rect类
作用:
类函数,包含四个成员变量。分别存储图像左上角的坐标和宽高。
函数形式:

C++:
template
cv::Rect_< Tp >::Rect (_Tp _x, _Tp _y, _Tp _width, _Tp _height )

参数解释(以C++展示的参数为例):
注意横轴为x轴,竖轴为y轴,横轴表示宽度,竖轴表示高度。对应前面图像的rows变量对应此处为y轴,cols变量对应此处为x轴,注意区分。
1._Tp _x:_Tp类型的_x,表示图像左上角x坐标
2._Tp _y:_Tp类型的_y,表示图像左上角y坐标
3. _Tp _width:_Tp类型的_width,表示图像x轴的宽度
4. _Tp _height:_Tp类型的_height,表示图像x轴的高度
代码示例:

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

int main() {
    
    
	Mat img = imread("cat.jpg");

	Mat roiImg = img(Rect(100, 300, 200, 300));

	namedWindow("BGR", WINDOW_AUTOSIZE);
	namedWindow("Roi", WINDOW_AUTOSIZE);
	imshow("BGR", img);
	imshow("Roi", roiImg);
	waitKey(0);
	return 0;
}

在这里插入图片描述

注意和C++ Rect的参数进行区别,python始终保持竖轴为x轴,表示高度,横轴为y轴,表示宽度,Rect竖轴为y轴,表示高度,横轴为x轴,表示宽度

# PYTHON
import cv2
import copy
def main():
    img = cv2.imread("cat.jpg")
    roiImg = copy.deepcopy(img[300:300+300, 100:100+200, :])

    cv2.namedWindow("BGR", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("BGR", img)
    cv2.namedWindow("python_Roi", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("python_Roi", roiImg)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

在这里插入图片描述

5.2 Range获取感兴趣区域[Range]

Range类
作用:
一个类函数,可以保存开始坐标和结束坐标。
函数形式:

C++:
Range (int _start, int _end)
参数解释(以C++展示的参数为例):
1.int _start:int类型的start变量,表示开始坐标。
2.int _end: int类型的end变量,表示结束坐标。
代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	//此时就和上例python类似,分别对图像row和col进行获取。
	//第一个参数获取row,第二个参数获取col。
	Mat roiImg = img(Range(300,300+300), Range(100, 100 + 200));
	namedWindow("BGR", WINDOW_AUTOSIZE);
	namedWindow("Roi", WINDOW_AUTOSIZE);
	imshow("BGR", img);
	imshow("Roi", roiImg);
	waitKey(0);
	return 0;
}

在这里插入图片描述

6 分解、合并图像通道

6.1 分解图像通道[split]

split()
作用:
将多通道数据划分为几个单通道数据。
函数形式:

C++:
void cv::split (const Mat & src, Mat* mvbegin)
Python:
cv.split(m[, mv]) -> mv

参数解释(以C++展示的参数为例):
1.const Mat & src:表示原始多通道图像数据。
2.Mat* mvbegin:输出数组;数组的数量必须匹配src.channels()。

代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	Mat channel[3];
	split(img, channel);
	namedWindow("BGR", WINDOW_NORMAL);
	namedWindow("B", WINDOW_NORMAL);
	namedWindow("G", WINDOW_NORMAL);
	namedWindow("A", WINDOW_NORMAL);
	imshow("BGR", img);
	imshow("B", channel[0]);
	imshow("G", channel[0]);
	imshow("A", channel[0]);
	waitKey(0);
	return 0;
}

在这里插入图片描述

#PYTHON
import cv2

def main():
    img = cv2.imread("cat.jpg")
    B, G, A = cv2.split(img)
    cv2.namedWindow("BGR", cv2.WINDOW_NORMAL)
    cv2.imshow("BGR", img)
    cv2.namedWindow("B_python", cv2.WINDOW_NORMAL)
    cv2.imshow("B_python", B)
    cv2.namedWindow("G_python", cv2.WINDOW_NORMAL)
    cv2.imshow("G_python", B)
    cv2.namedWindow("R_python", cv2.WINDOW_NORMAL)
    cv2.imshow("R_python", B)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

在这里插入图片描述

6.2 合并图像通道[merge]

merge()
作用:
将多个同类型,同大小,同深度的单通道数据,合并成多通道数据。
函数形式:

C++:
void cv::merge (const Mat * mv, size_t count, OutputArray dst )
Python:
cv.merge( mv[, dst] ) -> dst

引用自博主Captain_zw的原创文章:【C/C++】size_t详解
size_t简单理解为 unsigned int就可以了,64位系统中为 long unsigned int。具体细节可以取该播主的文章中了解。

参数解释(以C++展示的参数为例):

  1. const Mat * mv: 表示待合并矩阵的输入数组,mv中的所有矩阵必须具有相同的大小和相同的深度。
  2. size_t count:表示输入矩阵的个数,它必须大于零。
  3. OutputArray dst :输出数组的大小和深度与mv[0]相同,通道的数量将等于参数计数。

代码示例:

// C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	Mat channel[3];
	split(img, channel);
	Mat channl_RGB[3] = {
    
    channel[2], channel[1], channel[0]};

	Mat dst;
	merge(channl_RGB, 3, dst);
	namedWindow("merge_RGB", WINDOW_AUTOSIZE);
	imshow("merge_RGB", dst);
	waitKey(0);
	return 0;
}

在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("cat.jpg")
    B, G, R = cv2.split(img)
    RGB_img = cv2.merge((R, G, B))
    cv2.namedWindow("RGB", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("RGB", RGB_img)
    cv2.waitKey(0)
	#显示结果同上
if __name__ == "__main__":
    main()

7 图像混合[addWeighted]

addWeighted()
作用:
计算两个数组的加权和。
addWeighted函数计算两个数组的加权和如下所示:
d s t [ I ] = α ∗ s r c 1 [ I ] + β ∗ s r c 2 [ I ] + γ dst[I] =\alpha*src1[I]+\beta*src2[I]+\gamma dst[I]=αsrc1[I]+βsrc2[I]+γ
其中I是数组元素的多维索引。在多通道阵列的情况下,每个通道都独立处理。
当输出数组的深度为CV_32S时。就会存在内存溢出情况,甚至可能得到一个错误的符号的结果。

函数形式:

C++:
void cv::addWeighted (InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype = -1)
Python:
cv.addWeighted( src1, alpha, src2, beta, gamma[, dst[, dtype]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src1:Src1第一个输入数组。
2.double alpha: 第一个数组元素的权值。
3.InputArray src2:Src2第二个输入数组,其大小和通道号与src1相同。
4.double beta:第二个数组元素的权重。
5.double gamma:一个加到权重总和上的标量值。
6.OutputArray dst: 输出数组dst,具有与输入数组相同的大小和通道数量。
7.int dtype:输出数组可选深度。当两个输入数组具有相同的深度时,dtype可以设置为-1,这相当于src1.depth()。depth函数可参考官方文档
代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("cat.jpg");
	Mat logo = imread("sunKnight.jpg");
	Mat RoiImg = img(Range(100, 100 + logo.rows), Range(500, 500 + logo.cols));
	// RoiImg 所指向的矩阵数据任然是img,因此修改RoiImg,就是在修改img数据
	addWeighted(RoiImg, 0.1, logo, 0.5, 0, RoiImg);
	namedWindow("mix", WINDOW_AUTOSIZE);
	imshow("mix", img);
	namedWindow("logo", WINDOW_AUTOSIZE);
	imshow("logo", logo);

	waitKey(0);
	return 0;
}

注意:RoiImg 只是修改了矩阵头,所指向的矩阵数据任然是img。因此修改RoiImg,就是在修改img数据。如果需要复制数据,则需要借助clone函数和copyTo函数
在这里插入图片描述

# PYTHON
import cv2

def main():
    img = cv2.imread("cat.jpg")
    logo = cv2.imread("sunKnight.jpg")
    img[300:300 + logo.shape[0], 600:600 + logo.shape[1]] \
        = cv2.addWeighted(img[300:300 + logo.shape[0], 600:600 + logo.shape[1]], 0.1, logo, 0.5, 0)
    cv2.namedWindow("mix_python", cv2.WINDOW_AUTOSIZE)
    cv2.imshow("mix_python", img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

在这里插入图片描述

8.色彩变换[cvtColor]

cvtColor()
作用:
将图像从一种颜色空间转换为另一种颜色空间。

注意,OpenCV中默认的颜色格式通常被称为RGB,但它实际上是BGR(字节颠倒)

R、G和B通道值的常规范围为:

  • CV_8U图像0到255
  • CV_16U图像0到65535
  • 0到1 CV_32F图像

函数形式:

C++:
void cv::cvtColor (InputArray src, OutputArray dst, int code, int dstCn = 0)
Python:
cv.cvtColor( src, code[, dst[, dstCn]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src:src输入图像:8位unsigned, 16位unsigned (CV_16UC…),或单精度浮点。
2.OutputArray dst:dst输出与src相同大小和深度的图像。
3.int code:颜色空间转换标志。
4.int dstCn = 0:目标图像中的通道数;如果该参数为0,则通道的数量将自动从src和代码中继承。

code类型
RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换 cv::COLOR_BGR2RGB
cv::COLOR_RGB2BGR
cv::COLOR_RGBA2BGRA
cv::COLOR_BGRA2RGBA
向RGB和BGR图像中增添alpha通道 cv::COLOR_RGB2RGBA
cv::COLOR_BGR2BGRA
从RGB和BGR图像中去除alpha通道 cv::COLOR_RGBA2RGB
cv::COLOR_BGRA2BGR
从RBG和BGR颜色空间转换到灰度空间 cv::COLOR_RGB2GRAY
cv::COLOR_BGR2GRAY
cv::COLOR_RGBA2GRAY
cv::COLOR_BGRA2GRAY
从灰度空间转换到RGB和BGR颜色空间 cv::COLOR_GRAY2RGB
cv::COLOR_GRAY2BGR
cv::COLOR_GRAY2RGBA
cv::COLOR_GRAY2BGRA
RGB和BGR颜色空间与BGR565颜色空间之间的转换 cv::COLOR_RGB2BGR565
cv::COLOR_BGR2BGR565
cv::COLOR_BGR5652RGB
cv::COLOR_BGR5652BGR
cv::COLOR_RGBA2BGR565
cv::COLOR_BGRA2BGR565
cv::COLOR_BGR5652RGBA
cv::COLOR_BGR5652BGRA
灰度空间域BGR565之间的转换 cv::COLOR_GRAY2BGR555
cv::COLOR_BGR5552GRAY
RGB和BGR颜色空间与CIE XYZ之间的转换 cv::COLOR_RGB2XYZ
cv::COLOR_BGR2XYZ
cv::COLOR_XYZ2RGB
cv::COLOR_XYZ2BGR
RGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换 cv::COLOR_RGB2YCrCb
cv::COLOR_BGR2YCrCb
cv::COLOR_YCrCb2RGB
cv::COLOR_YCrCb2BGR
RGB和BGR颜色空间与HSV颜色空间之间的相互转换 cv::COLOR_RGB2HSV
cv::COLOR_BGR2HSV
cv::COLOR_HSV2RGB
cv::COLOR_HSV2BGR
RGB和BGR颜色空间与HLS颜色空间之间的相互转换 cv::COLOR_RGB2HLS
cv::COLOR_BGR2HLS
cv::COLOR_HLS2RGB
cv::COLOR_HLS2BGR
RGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换 cv::COLOR_RGB2Lab
cv::COLOR_BGR2Lab
cv::COLOR_Lab2RGB
cv::COLOR_Lab2BGR
RGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换 cv::COLOR_RGB2Luv
cv::COLOR_BGR2Luv
cv::COLOR_Luv2RGB
cv::COLOR_Luv2BGR
Bayer格式(raw data)向RGB或BGR颜色空间的转换 cv::COLOR_BayerBG2RGB
cv::COLOR_BayerGB2RGB
cv::COLOR_BayerRG2RGB
cv::COLOR_BayerGR2RGB
cv::COLOR_BayerBG2BGR
cv::COLOR_BayerGB2BGR
cv::COLOR_BayerRG2BGR
cv::COLOR_BayerGR2BGR

代码示例:
代码参考上一章OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间) 中的1.5.6 色彩空间示例章节。

9 缩放函数[resize]

resize()
作用:
对目标图像缩放到指定尺寸。
函数形式:

C++:
resize()
void cv::resize (InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR)
Python:
cv.resize( src, dsize[, dst[, fx[, fy[, interpolation]]]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src:输入图像
2.OutputArray dst:输出图像
3.Size dsize:输出图像的尺度大小。Size类是先宽度,后高度来构造对象的。Size_ (_Tp _width, _Tp _height)
4.double fx :横轴上的比例因子;当它等于0时,它被计算为 ( d o u b l e ) d s z i e . w i d t h / s r c . c o l s (double)dszie.width/src.cols (double)dszie.width/src.cols
5.double fy:竖轴上的比例因子;当它等于0时,它被计算为 ( d o u b l e ) d s z i e . h e i g h t / s r c . r o w s (double)dszie.height/src.rows (double)dszie.height/src.rows
6.int interpolation:插值方法。
机翻,如有疑问,可参考官方技术文档::InterpolationFlags

插值方法
INTER_NEAREST
Python: cv.INTER_NEAREST
最近邻插值法
INTER_LINEAR
Python: cv.INTER_LINEAR
双线性插值
INTER_CUBIC
Python: cv.INTER_CUBIC
双三次插值
INTER_AREA
Python: cv.INTER_AREA
利用像素面积关系重采样。它可能是图像抽取的首选方法,因为它能给出无摩尔纹的结果。但是当图像被放大时,它类似于inter_nearest方法。
INTER_LANCZOS4
Python: cv.INTER_LANCZOS4
8x8邻域上的Lanczos插值
INTER_LINEAR_EXACT
Python: cv.INTER_LINEAR_EXACT
位精确双线性插值
INTER_NEAREST_EXACT
Python: cv.INTER_NEAREST_EXACT
位精确最近邻插值。这将产生与PIL, scikit-image或Matlab中的最近邻方法相同的结果。
INTER_MAX
Python: cv.INTER_MAX
插值码掩码
WARP_FILL_OUTLIERS
Python: cv.WARP_FILL_OUTLIERS
标志,填充所有目标图像像素。如果其中一些与源图像中的异常值相对应,则将其设为0
WARP_INVERSE_MAP
Python: cv.WARP_INVERSE_MAP
标志,逆变换
举例, linearPolar或者logPolar 变换:
标志未设立: d s t ( ρ , ϕ ) = s r c ( x , y ) dst(ρ,ϕ)=src(x,y) dst(ρ,ϕ)=src(x,y)
标志设立: d s t ( x , y ) = s r c ( ρ , ϕ ) dst(x,y)=src(ρ,ϕ) dst(x,y)=src(ρ,ϕ)

代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	Mat img = imread("sunKnight.jpg");
	Mat dst;
	resize(img, dst, Size(512, 512));
	namedWindow("origin", WINDOW_AUTOSIZE);
	imshow("origin", img);
	namedWindow("resize", WINDOW_AUTOSIZE);
	imshow("resize", dst);
	waitKey(0);
	return 0;
}

在这里插入图片描述
注意,python版的cv2.resize,也是显示宽度,后是高度。

# PYTHON
import cv2
def main():
    img = cv2.imread("sunKnight.jpg")
    resizeImg = cv2.resize(img, (512, 256))
    cv2.namedWindow("Resize")
    cv2.imshow("Resize", resizeImg)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

在这里插入图片描述

10 图像绘制

10.1 基础类[Point/Scalar/Size/Mat]

10.1.1 Point类

Point类表示二维坐标系下的点,即由图像的x坐标和y坐标。经过测试,OpenCV统一都是横轴x,竖轴y,因此,此处x指的是width,y指的是heigth。
用法如下,只列举一个构造函数,其他初始化方法,请查询源码:

Point point;
point.x=10;
point.y =20;
//或者
Point point = Point(10 ,20)
//另外还有如下定义
typedef Point_<int> Point2i;
typedef Point_<int64> Point2l;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
typedef Point2i Point;

10.1.2 Scalar类

Scalar类表示四个元素的数组。用来传递像素值,如RGB颜色值。对于Scalar而言,如果只有三个参数,则第四个参数不需要写出来。
用法如下,只列举一个构造函数,其他初始化方法,请查询源码:
Scalar(b, g, r, a);

template<typename _Tp> inline
Scalar_<_Tp>::Scalar_(_Tp v0, _Tp v1, _Tp v2, _Tp v3)
{
    
    
    this->val[0] = v0;
    this->val[1] = v1;
    this->val[2] = v2;
    this->val[3] = v3;
}
//另外还有如下定义
typedef Scalar_<double> Scalar;

10.1.3 Size类

Size表示矩阵数据的宽度和高度。
用法如下,只列举一个构造函数,其他初始化方法,请查询源码:
Size(_Tp width, _Tp height)

template<typename _Tp> inline
Size_<_Tp>::Size_(_Tp _width, _Tp _height)
    : width(_width), height(_height) {
    
    }
   
//另外还有如下定义
typedef Size_<int> Size2i;
typedef Size_<int64> Size2l;
typedef Size_<float> Size2f;
typedef Size_<double> Size2d;
typedef Size2i Size;

10.1.4 Mat类的初始化方法

Mat类是Opencv专门用来存储多维矩阵数据的。本节只介绍Mat类的初始化方法。值得注意的是Mat类为了减少数据的存储,它的拷贝构造函数一般只保存包含尺寸,维数,数据存储地址等信息的矩阵头,不复制数据矩阵,如果需要深度拷贝,则需要借助clone()和copyTo()函数。

补充说明:
形式:CV_<bit_depth>(S|U|F)C<number_of_channels>

  • bit_depth:比特数—代表8bite,16bites,32bites,64bites…
  • S|U|F:
    S–代表—signed int—有符号整形
    U–代表–unsigned int–无符号整形
    F–代表–float---------单精度浮点型
  • C<number_of_channels>----代表—一张图片的通道数,比如:
    channels = 1:灰度图片–grayImg—是–单通道图像
    channels = 3:RGB彩色图像---------是–3通道图像
    channels = 4:带Alph通道的RGB图像–是–4通道图像
    channels = (n):自己进行设定

初始化方法如下:

10.1.4.1 通过Mat()构造函数来初始化(rows, cols, type, scalar)

//C++
#include<opencv2/opencv.hpp>
#include<String>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
    
    
	//Mat(rows, cols, type, scalar)
	Mat img(2, 2, CV_8UC3, Scalar(0 ,0 ,255));
	cout << img << endl;
	return 0;
}

在这里插入图片描述
Mat数据存储的方式是依次存储每个位置上所有通道像素数后再存储下一个。因此它的尺度为(rows × \times ×(col*channels))

10.1.4.2 通过create方法初始化[create]

//C++
#include<opencv2/opencv.hpp>
#include<String>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
    
    
	Mat img;
	//Mat(rows, cols, type)
	img.create(4, 4, CV_8UC(2));
	cout << img << endl;
	return 0;
}

注意此方法不能设置矩阵初值。
在这里插入图片描述

10.1.4.3 用过[eye/ones/zeros]来初始化

//C++
#include<opencv2/opencv.hpp>
#include<String>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
    
    
	//Mat(rows, cols, type)
	Mat eyeImg = Mat::eye(4,4,CV_64F);
	cout << "eyeImg = " << eyeImg << endl;
	//Mat(rows, cols, type)
	Mat onesImg = Mat::ones(4, 4, CV_32F);
	cout << "onesImg = " << onesImg << endl;
	//Mat(rows, cols, type)
	Mat zerosImg = Mat::zeros(4, 4, CV_8U);
	cout << "zerosImg = " << zerosImg << endl;
	return 0;
}

在这里插入图片描述

10.1.4.4 直接输入值进行初始化

通过Mat_类型的构造函数如下,进行初始化。

template<typename _Tp> inline
Mat_<_Tp>::Mat_(int _rows, int _cols)
    : Mat(_rows, _cols, traits::Type<_Tp>::value)
{
    
    
}
//C++
#include<opencv2/opencv.hpp>
#include<String>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
    
    
	Mat Img = (Mat_<double>(3, 3) << 0, -1, 0, -1, 0, 0, 0, 0, -1);
	cout << Img << endl;
	return 0;
}

在这里插入图片描述

10.2 绘制线[line]

line()
作用:
绘制连接两点(pt1, pt2)的线段。这条线被图像边界截断。对于具有整数坐标的非反锯齿线,使用8连通或4连通的布雷森汉姆算法。粗线用圆角结尾。使用高斯滤波绘制反锯齿线。
函数形式:

C++:
void cv::line (InputOutputArray img, Point pt1, Point pt2, const Scalar & color, int thickness = 1,
int lineType = LINE_8, int shift = 0)
Python:
cv.line( img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img

参数解释(以C++展示的参数为例):
1.InputOutputArray img:输入图像
2.Point pt1:坐标的起始点
3.Point pt2:坐标的结束点
4.const Scalar & color:表示线段的颜色
5.int thickness:表示线段的粗细,默认为1
6. int lineType:线段的类型,默认为LINE_8
7. int shift:点坐标中小数位的个数。默认为0

线段类型 介绍
FILLED
Python: cv.FILLED
填满
LINE_4
Python: cv.LINE_4
4邻接线
LINE_8
Python: cv.LINE_8
8邻接线
LINE_AA
Python: cv.LINE_AA
抗锯齿线

代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	namedWindow("Drawing board");
	Mat img = Mat::zeros(512, 512, CV_8UC3);
	Point pt1(256, 0);
	Point pt2(256, 512);
	Scalar color(0, 0, 255);
	line(img, pt1, pt2, color, 1, 8, 0);
	
	//也可以直接传
	line(img, Point(0, 256), Point(512, 256), Scalar(0, 0, 255), 1, 8, 0);
	imshow("Drawing board", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

# PYTHON
import cv2
import numpy as np
def main():
    img = np.zeros((512,512, 3), np.uint8)
    cv2.line(img, (256, 0), (256, 512), (0, 0, 255), 1, 8, 0)
    cv2.line(img, (0, 256), (512, 256), (0, 0, 255), 1, 8, 0)
    cv2.namedWindow("Drawing board")
    cv2.imshow("Drawing board", img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

10.3 绘制圆[circle]

circle()
作用:
绘制一个圆
函数形式:

C++:
void cv::circle(InputOutputArray img, Point center, int radius, const Scalar & color,
int thickness = 1, int lineType = LINE_8, int shift = 0)
Python:
cv.circle( img, center, radius, color[, thickness[, lineType[, shift]]] ) -> img

参数解释(以C++展示的参数为例):
1.InputOutputArray img:输入图像
2.Point center:圆的中心点
3.int radius:半径
4.const Scalar & color:绘制圆的线段颜色
5.int thickness:线段粗细,默认为1。如果是负值,表示要绘制一个已填充的圆。
6.int lineType :线段类型。默认为8
7.int shift :点坐标中小数位的个数。默认为0

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>
#include<typeinfo>
using namespace std;
using namespace cv;
int main() {
    
    
	namedWindow("Drawing board");
	Mat img = Mat::zeros(512, 512, CV_8UC3);
	Point pt1(256, 0);
	Point pt2(256, 512);
	Scalar color(0, 0, 255);
	line(img, pt1, pt2, color, 1, 8, 0);
	line(img, Point(0, 256), Point(512, 256), Scalar(0, 0, 255), 1, 8, 0);

	circle(img, Point(256, 256), 10, Scalar(100, 100, 100), -1, 8, 0);
	imshow("Drawing board", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

#PYTHON
import cv2
import numpy as np

def main():
    img = np.zeros((512,512, 3), np.uint8)
    cv2.line(img, (256, 0), (256, 512), (0, 0, 255), 1, 8, 0)
    cv2.line(img, (0, 256), (512, 256), (0, 0, 255), 1, 8, 0)
    cv2.circle(img, (256, 256), 10, (100, 100, 100), -1, 8, 0)
    cv2.namedWindow("Drawing board")
    cv2.imshow("Drawing board", img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

10.4 绘制椭圆[ellipse]

ellipse ()
作用:
绘制简单或粗椭圆弧或填充椭圆扇区。

具有更多参数的函数cv::ellipse绘制椭圆轮廓、填充椭圆、椭圆圆弧或填充椭圆扇形。绘图代码使用一般参数形式。采用分段线性曲线逼近椭圆弧边界。如果你需要更多的椭圆渲染控制,你可以使用ellipse2Poly检索曲线,然后用polylines渲染它或用fillPoly填充它。如果您使用函数的第一个变体,并想要绘制整个椭圆,而不是圆弧,传递startAngle=0和endAngle=360。如果startAngle大于endAngle,则交换它们。下图解释了绘制蓝色圆弧的参数含义。
在这里插入图片描述

函数形式:

C++:
void cv::ellipse (InputOutputArray img , Point center, Size axes, double angle, double startAngle,
double endAngle, const Scalar & color, int thickness = 1, int lineType = LINE_8, int shift = 0)
Python:
cv.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]] ) -> img
cv.ellipse(img, box, color[, thickness[, lineType]]

参数解释(以C++展示的参数为例):
1.InputOutputArray img:输入图像
2.Point center:圆的中心点
3.Size axes:半长轴和半短轴
4.double angle:旋转角度,单位是度
5.double startAngle:圆弧起始角度,单位是度
6.double endAngle:圆弧结束角度,单位是度
7.const Scalar & color:绘制圆的线段颜色
8.int thickness:线段粗细,默认为1。如果是负值,表示要绘制一个已填充的椭圆。
9.int lineType :线段类型。默认为8
10.int shift :点坐标中小数位的个数。默认为0
代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	namedWindow("Drawing board");
	Mat img = Mat::zeros(512, 512, CV_8UC3);
	Point pt1(256, 0);
	Point pt2(256, 512);
	Scalar color(0, 0, 255);
	line(img, pt1, pt2, color, 1, 8, 0);
	line(img, Point(0, 256), Point(512, 256), Scalar(0, 0, 255), 1, 8, 0);
	circle(img, Point(256, 256), 10, Scalar(100, 100, 100), -1, 8, 0);
	//注意size类是先宽度,后高度, X轴是起点,顺时针旋转。
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 0, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 45, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 90, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 135, 0, 360, Scalar(0, 255, 0), 1, 8, 0);

	imshow("Drawing board", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

#PYTHON
import cv2
import numpy as np

def main():
    img = np.zeros((512,512, 3), np.uint8)
    cv2.line(img, (256, 0), (256, 512), (0, 0, 255), 1, 8, 0)
    cv2.line(img, (0, 256), (512, 256), (0, 0, 255), 1, 8, 0)
    cv2.circle(img, (256, 256), 10, (100, 100, 100), -1, 8, 0)
    # axes 是先宽度,后高度,x轴为起点,顺时针旋转
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 0, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 45, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 90, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 135, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.namedWindow("Drawing board")
    cv2.imshow("Drawing board", img)
    cv2.waitKey(0)
    
if __name__ == "__main__":
    main()

10.3 绘制矩形[rectangle]

rectangle()
作用:
绘制一个矩形。
函数形式:

C++:
◆ rectangle() 【1/2】
void cv::rectangle (InputOutputArray img, Point pt1, Point pt2, const Scalar & color, int thickness = 1, int lineType = LINE_8, int shift = 0)
Python:
cv.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img
cv.rectangle(img, rec, color[, thickness[, lineType[, shift]]] ) -> img

◆ rectangle() 【2/2】
void cv::rectangle(InputOutputArray img, Rect rec, const Scalar & color, int thickness = 1, int lineType = LINE_8, int shift = 0)
Python:
cv.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img
cv.rectangle(img, rec, color[, thickness[, lineType[, shift]]] ) -> img

参数解释(以C++展示的参数为例):

1.InputOutputArray img:输入图像
2.Point pt1:Point类的pt1对象,是坐标的起始点
3.Point pt2:Point类的pt2对象,是坐标的结束点
4.const Scalar & color:表示线段的颜色
5.int thickness:表示线段的粗细,默认为1,如果是负值,表示要绘制一个已填充的矩形。
6.int lineType:线段的类型,默认为LINE_8
7.int shift:点坐标中小数位的个数。默认为0

1.InputOutputArray img:输入图像
2.Rect rec:Rect类的数据,表示图像的范围
3.const Scalar & color:表示线段的颜色
4.int thickness:表示线段的粗细,默认为1,如果是负值,表示要绘制一个已填充的矩形。
5.int lineType:线段的类型,默认为LINE_8
6.int shift:点坐标中小数位的个数。默认为0

代码示例:

//C++
#include<opencv2/opencv.hpp>
using namespace cv;
int main() {
    
    
	namedWindow("Drawing board");
	Mat img = Mat::zeros(512, 512, CV_8UC3);
	Point pt1(256, 0);
	Point pt2(256, 512);
	Scalar color(0, 0, 255);
	line(img, pt1, pt2, color, 1, 8, 0);
	line(img, Point(0, 256), Point(512, 256), Scalar(0, 0, 255), 1, 8, 0);
	circle(img, Point(256, 256), 10, Scalar(100, 100, 100), -1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 0, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 45, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 90, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 135, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	//调用方式一。
	rectangle(img, Point(256 - 10, 256 - 10), Point(256 + 10, 256 + 10), Scalar(255, 0, 0), 1, 8, 0);
	//调用方式二。
	rectangle(img, Rect(256 - 512 / 4, 256 - 512 / 4, 512 / 2, 512 / 2), Scalar(255, 0, 0), 1, 8, 0);
	imshow("Drawing board", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

#PYTHON
import cv2
import numpy as np

def main():
    img = np.zeros((512,512, 3), np.uint8)
    cv2.line(img, (256, 0), (256, 512), (0, 0, 255), 1, 8, 0)
    cv2.line(img, (0, 256), (512, 256), (0, 0, 255), 1, 8, 0)
    cv2.circle(img, (256, 256), 10, (100, 100, 100), -1, 8, 0)
    # axes 是先宽度,后高度,x轴为起点,顺时针旋转
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 0, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 45, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 90, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 135, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.rectangle(img, (256 - 10, 256 - 10), (256 + 10, 256 + 10), (255, 0, 0), 1, 8, 0)
    cv2.rectangle(img, (256 - 512 // 4, 256 - 512 // 4, 512 // 2, 512 // 2), (255, 0, 0), 1, 8, 0)
    cv2.namedWindow("Drawing board")
    cv2.imshow("Drawing board", img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

10.4 绘制多边形[polylines]

polylines()
作用:
绘制多个多边形曲线。
函数形式:

C++:
void cv::polylines (InputOutputArray img, InputArrayOfArrays pts, bool isClosed, const Scalar & color, int thickness = 1, int lineType = LINE_8, int shift = 0 )
Python:
cv.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]] ) -> img

参数解释(以C++展示的参数为例):
1.InputOutputArray img:输入图像
2.InputArrayOfArrays pts:多边形曲线点集。
3.bool isClosed:标志,表示绘制的折线是否关闭。如果它们是闭合的,函数从每条曲线的最后一个顶点到它的第一个顶点绘制一条直线。
4.const Scalar & color:表示线段的颜色
5.int thickness:表示线段的粗细。
6.int lineType:线段的类型,默认为LINE_8
7.int shift:点坐标中小数位的个数。默认为0

代码示例:
注意:InputArray这个接口类可以是Mat、Mat_、Mat_<T, m, n>、vector、vector<vector>、vector。OutputArray是InputArray的派生类。

//C++
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
    
    
	namedWindow("Drawing board");
	Mat img = Mat::zeros(512, 512, CV_8UC3);
	Point pt1(256, 0);
	Point pt2(256, 512);
	Scalar color(0, 0, 255);
	line(img, pt1, pt2, color, 1, 8, 0);
	line(img, Point(0, 256), Point(512, 256), Scalar(0, 0, 255), 1, 8, 0);
	circle(img, Point(256, 256), 10, Scalar(100, 100, 100), -1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 0, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 45, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 90, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 135, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	rectangle(img, Point(256 - 10, 256 - 10), Point(256 + 10, 256 + 10), Scalar(255, 0, 0), 1, 8, 0);
	rectangle(img, Rect(256 - 512 / 4, 256 - 512 / 4, 512 / 2, 512 / 2), Scalar(255, 0, 0), 1, 8, 0);

	vector<Point> pts(4);
	pts[0] = Point(256, 0);
	pts[1] = Point(512, 256);
	pts[2] = Point(256, 512);
	pts[3] = Point(0, 256);
	polylines(img, pts, true, Scalar(0, 100, 100), 1, 8, 0);
	imshow("Drawing board", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

#PYTHON
import cv2
import numpy as np
def main():
    img = np.zeros((512,512, 3), np.uint8)
    cv2.line(img, (256, 0), (256, 512), (0, 0, 255), 1, 8, 0)
    cv2.line(img, (0, 256), (512, 256), (0, 0, 255), 1, 8, 0)
    cv2.circle(img, (256, 256), 10, (100, 100, 100), -1, 8, 0)
    # axes 是先宽度,后高度,x轴为起点,顺时针旋转
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 0, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 45, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 90, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 135, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.rectangle(img, (256 - 10, 256 - 10), (256 + 10, 256 + 10), (255, 0, 0), 1, 8, 0)
    cv2.rectangle(img, (256 - 512 // 4, 256 - 512 // 4, 512 // 2, 512 // 2), (255, 0, 0), 1, 8, 0)
   
    pts = np.array([[256, 0], [512, 256], [256, 512], [0, 256]], np.int32)
    pts = pts.reshape((-1, 1, 2))
    cv2.polylines(img, [pts], True, (0, 100, 100), 1, 8, 0)
    cv2.namedWindow("Drawing board")
    cv2.imshow("Drawing board", img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

10.5 在图像中绘制文字字符串[putText]

putText()
作用:
绘制文本字符串。函数cv::putText在图像中呈现指定的文本字符串。不能使用指定字体呈现的符号将替换为问号。

函数形式:

C++:
void cv::putText(InputOutputArray img, const String & text, Point org, int fontFace, double fontScale, Scalar color, int thickness = 1, int lineType = LINE_8, bool bottomLeftOrigin = false )
Python:
cv.putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]]) -> img

参数解释(以C++展示的参数为例):
1.InputOutputArray img: 输入图像
2.const String & text:需要展示的字符串
3.Point org:图像中文本字符串的左下角坐标。
4.int fontFace:字体类型
5.double fontScale:字体大小,表示与字体的基本大小相乘的字体比例因子。
6.Scalar color:表示线段的颜色
7.int thickness:表示线段的粗细。
8.int lineType:线段的类型,默认为LINE_8
9.bool bottomLeftOrigin = false 当为true时,图像数据来源位于左下角。否则,它在左上角。

字体类型 解释
FONT_HERSHEY_SIMPLEX 正常大小无衬线(黑体)字体
FONT_HERSHEY_PLAIN 小尺寸无衬线(黑体)字体
FONT_HERSHEY_DUPLEX 正常大小的无衬线字体(比FONT_HERSHEY_SIMPLEX更复杂)
FONT_HERSHEY_COMPLEX 正常大小的衬线字体
FONT_HERSHEY_TRIPLEX 正常大小的衬线字体(比FONT_HERSHEY_COMPLEX更复杂)
FONT_HERSHEY_COMPLEX_SMALL FONT_HERSHEY_COMPLEX的小版本
FONT_HERSHEY_SCRIPT_SIMPLEX 书写风格的字体
FONT_HERSHEY_SCRIPT_COMPLEX 更复杂的变体FONT_HERSHEY_SCRIPT_SIMPLEX
FONT_ITALIC 斜体标志

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<String>
using namespace std;
using namespace cv;
int main() {
    
    
	namedWindow("Drawing board");
	Mat img = Mat::zeros(512, 512, CV_8UC3);
	Point pt1(256, 0);
	Point pt2(256, 512);
	Scalar color(0, 0, 255);
	line(img, pt1, pt2, color, 1, 8, 0);
	line(img, Point(0, 256), Point(512, 256), Scalar(0, 0, 255), 1, 8, 0);
	circle(img, Point(256, 256), 10, Scalar(100, 100, 100), -1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 0, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 45, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 90, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	ellipse(img, Point(256, 256), Size(512 / 4, 512 / 16), 135, 0, 360, Scalar(0, 255, 0), 1, 8, 0);
	rectangle(img, Point(256 - 10, 256 - 10), Point(256 + 10, 256 + 10), Scalar(255, 0, 0), 1, 8, 0);
	rectangle(img, Rect(256 - 512 / 4, 256 - 512 / 4, 512 / 2, 512 / 2), Scalar(255, 0, 0), 1, 8, 0);
	vector<Point> pts(4);
	pts[0] = Point(256, 0);
	pts[1] = Point(512, 256);
	pts[2] = Point(256, 512);
	pts[3] = Point(0, 256);
	polylines(img, pts, true, Scalar(0, 100, 100), 1, 8, 0);
	
	String text = "Atomic structure diagram";
	putText(img, text, Point(256 - 512 / 4, 256 - 512 / 4), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 255, 255), 1, 8, false);
	imshow("Drawing board", img);
	waitKey(0);
	return 0;
}

在这里插入图片描述

#PYTHON
import cv2
import numpy as np

def main():
    img = np.zeros((512,512, 3), np.uint8)
    cv2.line(img, (256, 0), (256, 512), (0, 0, 255), 1, 8, 0)
    cv2.line(img, (0, 256), (512, 256), (0, 0, 255), 1, 8, 0)
    cv2.circle(img, (256, 256), 10, (100, 100, 100), -1, 8, 0)
    # axes 是先宽度,后高度,x轴为起点,顺时针旋转
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 0, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 45, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 90, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.ellipse(img, (256, 256), (512 // 4, 512 // 16), 135, 0, 360, (0, 255, 0), 1, 8, 0)
    cv2.rectangle(img, (256 - 10, 256 - 10), (256 + 10, 256 + 10), (255, 0, 0), 1, 8, 0)
    cv2.rectangle(img, (256 - 512 // 4, 256 - 512 // 4, 512 // 2, 512 // 2), (255, 0, 0), 1, 8, 0)
    pts = np.array([[256, 0], [512, 256], [256, 512], [0, 256]], np.int32)
    pts = pts.reshape((-1, 1, 2))
    cv2.polylines(img, [pts], True, (0, 100, 100), 1, 8, 0)

    cv2.putText(img, "Atomic structure diagram",(256 - 512 // 4, 256 - 512 // 4), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
                (255, 255, 255), 1, 8, False)

    cv2.namedWindow("Drawing board")
    cv2.imshow("Drawing board", img)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

总结

本文仅对我认为常用的函数进行总结,在查看Opencv官方文档中,还有许多函数未介绍。如果本文没有写的,可以查询OpenCV官方文档。感谢各位读者的阅读。如果您觉得对您有帮助的话,可以给小弟一个赞。

最后,缅怀毛星云先生,感谢毛星云先生的引领,本文主要参考了毛星云先生的《OpenCV3编程入门》。

猜你喜欢

转载自blog.csdn.net/weixin_43610114/article/details/126016394