C++ Opencv

参考资料

第一章 OpenCV 基础

一、 OpenCV 环境配置与搭建

1、安装 OpenCV

  1. 下载安装包

    地址:https://opencv.org/releases/

  2. 双击安装包

  3. 选择安装路径

2、安装 VS2019

  1. 下载安装包

    下载地址:https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/

3、创建项目

4、配置环境

(1) 属性配置

(2) 包含目录配置

(3) 配置库目录

(4) 配置连接器

(5) 最后的确定

5、我的第一个案例

#include <iostream>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
	// 1. 打开图片
	Mat src = imread("F:/datasets/Test Sample/images/crowd_mask38.jpg");
	// 2. 显示
	imshow("frame", src);
	// 3. 等待 0: 表示一直等待,其他单位是毫秒
	waitKey(0);
	// 4. 关闭所有窗口
	destroyAllWindows();
	// 5. 等待用户输入
	system("pause");
	return 0;
}

二、图像读取与显示

1、知识点

(1) imread

作用:读取图像

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

参数解释:

  • filename:要加载的文件名
  • flags: 可以采用cv:: ImreadModes 的标志,函数默认是采用 IMREAD_COLOR,即为按三通道读入。
    • IMREAD_COLOR 或者 1: imread按三通道方式读入图像,即彩色图像
    • IMREAD_GRAYSCALE 或者 0: imread按单通道的方式读入图像,即灰度图像
    • IMREAD_UNCHANGED 或者 -1: imread按解码得到的方式读入图像

返回值:Mat 矩阵

(2) namedWindow

作用:创建窗口

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

参数解释:

  • winname:窗口名称,用作窗口的标识符
  • flags:窗口属性设置标志
    • WINDOW_NORMAL = 0x00000000: 显示图像后,允许用户随意调整窗口大小
    • WINDOW_AUTOSIZE = 0x00000001: 根据图像大小显示窗口,不允许用户调整大小
    • WINDOW_OPENGL = 0x00001000: 创建窗口的时候会支持OpenGL
    • WINDOW_FULLSCREEN = 1:全屏显示窗口
    • WINDOW_FREERATIO = 0x00000100:调整图像尺寸以充满窗口
    • WINDOW_KEEPRATIO = 0x00000000:保持图像的比例
    • WINDOW_GUI_EXPANDED = 0x00000000:创建的窗口允许添加工具栏和状态栏
    • WINDOW_GUI_NORMAL = 0x00000010:创建没有状态栏和工具栏的窗口

(3) imshow

作用:显示图像

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

参数解释:

  • winname:要显示图像的窗口的名字,用字符串形式赋值
  • mat:要显示的图像矩阵

(4) waitKey

作用:waitKey()函数的功能是不断刷新图像,频率时间为delay,单位为ms。 返回值为当前键盘按键值

语法:int waitKey(int delay = 0);

参数解释:

  • delay 频率单位毫秒

返回值:当前键盘按键值 int 类型 ESC = 27

(5) destroyAllWindows

作用:关闭所有窗口

语法:void destroyAllWindows();

(6) empty()

作用:检查文件是否正确

语法:bool empty() const

2、示例

#include <iostream>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
	// 1. 打开图片
	Mat src = imread("F:/datasets/Test Sample/images/crowd_mask38.jpg");
	// 灰度图
	// Mat src = imread("F:/datasets/Test Sample/images/crowd_mask38.jpg", IMREAD_GRAYSCALE);
	// 读取带透明通道的图
	// Mat src = imread("F:/datasets/Test Sample/images/crowd_mask38.jpg", IMREAD_UNCHANGED);
	if (src.empty())
	{
		cout << "图片读取失败!!!" << endl;
		return -1;
	}
	// 2. 创建一个窗口
	namedWindow("输入窗口", WINDOW_FREERATIO);
	
	// 3. 显示
	imshow("输入窗口", src);
	
	// 4. 等待 0: 表示一直等待,其他单位是毫秒
	waitKey(0);
	
	// 5. 关闭所有窗口
	destroyAllWindows();
	
	// 6. 等待用户输入
	system("pause");
	return 0;
}

三、图像色彩空间转换

1、知识点

  1. 色彩空间转换函数-cvtColor

  2. 图像保存-imwrite

    第一个参数是

    第二个参数是

(1) cvtColor

作用:色彩空间转换函数

语法:void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );

参数解释:

  • src:输入图像
  • dst:输出图像
  • code:代码颜色空间转换代码
    • COLOR_BGR2GRAY = 6:彩色到灰度
    • COLOR_GRAY2BGR = 8:灰度到彩色
    • COLOR_BGR2HSV = 40:BGR到HSV
    • COLOR_HSV2BGR = 54:HSV到BGR
    • 更多请查看:cv::ColorConversionCodes
  • dstCn:目标图像中的信道数; 如果该参数为0,则通道的数量自动从SRC和code派生。

(2) imwrite

作用:图像保存

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

参数解释:

  • filename:图像保存路径
  • img:为图像数据来源,其类型为Mat。
  • params:用来设置对应图片格式的参数的,因为一般情况下这些图片格式都是经过了压缩的,这里就是设置这些压缩参数来控制图片的质量。该参数是一个vector<int>类型,里面分别存入paramId_1, paramValue_1, paramId_2, paramValue_2, ... 也就是说存入一对属性值。如果不设置该参数的话,则程序会自动根据所保存的图像格式采用一个默认的参数。

返回值:成功: true; 失败: false;

2、示例

  • 创建 quickopencv.h 头文件

  • 创建 QuickDemo 类 和 void colorSpaceConversion(Mat& img);

    #pragma once
    #include <iostream>
    #include <opencv2/opencv.hpp>
    
    using namespace cv;
    using namespace std;
    
    class QuickDemo
    {
    public:
    	// 1. 色彩空间转换
    	void colorSpaceConversion(Mat& img);
    };
    
  • 创建 quickopencv.cpp 源文件

  • 实现 void colorSpaceConversion(Mat& img);

    #include "quickopencv.h"
    
    void QuickDemo::colorSpaceConversion(Mat& img)
    {
    	/*
    		H: 0 ~ 180; S V
    		B: 0 ~ 255; G: 0 ~ 255; R: 0 ~ 255; 
    	*/
    	// 定义 灰度图 和 hsv 变量
    	Mat gray, hsv;
    	// 图像色彩空间转换
    	cvtColor(img, hsv, COLOR_BGR2HSV);
    	cvtColor(img, gray, COLOR_BGR2GRAY);
    	// 显示图像
    	imshow("hsv", hsv);
    	imshow("gray", gray);
    	// 保存图像
    	imwrite("F:/projects/C++/OpenCV_projects/demo01/images/hsv.png", hsv);
    	imwrite("images/gray.png", gray);
    }
    
  • 测试 图像色彩空间转换函数

  • 02 图像色彩空间转换.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/new_116.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 调用色彩空间转换函数
    	qd.colorSpaceConversion(src);
    	
    	// 5. 阻塞
    	waitKey(0);
    	
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    	
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

四、图像对象的创建与赋值

1、知识点

  • OpenCV 中图像对象创建与赋值
  • C++ 中 Mat 对象与创建
  • Python中Numpy 数组

(1) Mat.clone()

作用:深拷贝

语法:GpuMat GpuMat::clone() const

(2) Mat.copyTo()

作用:深拷贝

语法:void GpuMat::copyTo(OutputArray dst, InputArray mask) const

参数解释:

  • dst:要拷贝的图像
  • mask:掩码

(3) Mat::zeros()

作用:创建空白图像

语法:static UMat zeros(Size size, int type)

参数说明:

  • size:图像尺寸
  • type:图像类型
    • CV_8UC1:单通道
    • CV_8UC2:双通道
    • CV_8UC3:三通道
    • CV_8UC4:四通道

(4) Mat.cols

作用:获取图像宽度

返回值:int

(5) Mat.rows

作用:获取图像高度

返回值:int

(6) Mat.channels()

作用:返回图像通道数

返回值: int

(7) Size()

作用:设置图像尺寸

语法:Size_<_Tp>::Size_(_Tp _width, _Tp _height)

参数解释:

  • _width:图像宽度单位 px
  • _height:图像高度单位 px

(8) Scalar()

作用:设置图像颜色

语法:Scalar_<_Tp>::Scalar_(_Tp v0, _Tp v1, _Tp v2, _Tp v3)

参数解释:

  • v0:B 通道的值 0 ~ 255
  • v1:G 通道的值 0 ~ 255
  • v2:R 通道的值 0 ~ 255
  • v3:A 通道的值 0 ~ 255

2、示例

  • 在头文件的 QuickDemo 中添加 void matCreation(Mat& img); 成员函数

    	// 2. 图像对象的创建与赋值
    	void matCreation(Mat& img);
    
  • 在源文件中 实现 void matCreation(Mat& img);

    
    void QuickDemo::matCreation(Mat& img)
    {
    	Mat m1, m2;
    	// 克隆
    	m1 = img.clone();
    	
    	// 拷贝
    	img.copyTo(m2);
    
    	// 创建空白图像 CV_8UC1: 单通道; CV_8UC3: 三通道;
    	// Mat m3 = Mat::ones(Size(8, 8), CV_8UC1);
    	Mat m3 = Mat::zeros(Size(400, 400), CV_8UC3);
    	// 获取信息
    	// std::cout << m3 << std::endl;
    	cout << "图像宽度为: " << m3.cols << endl;
    	cout << "图像高度为: " << m3.rows << endl;
    	cout << "图像通道数为: " << m3.channels() << endl;
    
    	// 通道赋值 - 单通道
    	m3 = 127;
    	// std::cout << m3 << std::endl;
    
    	// 通道赋值 - 三通道
    	m3 = Scalar(255, 255, 0);
    	// std::cout << m3 << std::endl;
    
    	// 显示图像
    	imshow("m3-1", m3);
    
    	// 拷贝构造(浅拷贝)会改变原始值
    	Mat m4 = m3;
    	m4 = Scalar(0, 255, 255);
    	imshow("m3-2", m3);
    
    	// 拷贝构造(深拷贝)不会改变原始值
    	Mat m5 = m3.clone();
    	m5 = Scalar(0, 255, 0);
    	imshow("m3-3", m3);
    
    	// 
    	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    }
    
  • 测试 图像对象的创建与赋值函数

  • 03 图像对象的创建与赋值.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/new_116.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 图像对象的创建与赋值函数
    	qd.matCreation(src);
    	
    	// 5. 阻塞
    	waitKey(0);
    	
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    	
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

五、图像像素的读写操作

1、知识点

  • OpenCV 中图像像素读写操作
  • C++ 中的像素遍历与访问
    • 数组遍历
    • 指针方式遍历
  • Python 中的像素遍历与访问
    • 数组遍历

2、示例

  • 在头文件的 QuickDemo 中添加 void pixelVisit(Mat& img); 成员函数

    	// 3. 图像像素的读写操作
    	void pixelVisit(Mat& img);
    
  • 在源文件中 实现 void pixelVisit(Mat& img);

    #include "quickopencv.h"
    
    void QuickDemo::colorSpaceConversion(Mat& img)
    {
    	/*
    		H: 0 ~ 180; S V
    		B: 0 ~ 255; G: 0 ~ 255; R: 0 ~ 255; 
    	*/
    	// 定义 灰度图 和 hsv 变量
    	Mat gray, hsv;
    	// 图像色彩空间转换
    	cvtColor(img, hsv, COLOR_BGR2HSV);
    	cvtColor(img, gray, COLOR_BGR2GRAY);
    	// 显示图像
    	imshow("hsv", hsv);
    	imshow("gray", gray);
    	// 保存图像
    	imwrite("F:/projects/C++/OpenCV_projects/demo01/images/hsv.png", hsv);
    	imwrite("images/gray.png", gray);
    }
    
    void QuickDemo::matCreation(Mat& img)
    {
    	Mat m1, m2;
    	// 克隆
    	m1 = img.clone();
    	
    	// 拷贝
    	img.copyTo(m2);
    
    	// 创建空白图像 CV_8UC1: 单通道; CV_8UC3: 三通道;
    	// Mat m3 = Mat::ones(Size(8, 8), CV_8UC1);
    	Mat m3 = Mat::zeros(Size(400, 400), CV_8UC3);
    	// 获取信息
    	// std::cout << m3 << std::endl;
    	cout << "图像宽度为: " << m3.cols << endl;
    	cout << "图像高度为: " << m3.rows << endl;
    	cout << "图像通道数为: " << m3.channels() << endl;
    
    	// 通道赋值 - 单通道
    	m3 = 127;
    	// std::cout << m3 << std::endl;
    
    	// 通道赋值 - 三通道
    	m3 = Scalar(255, 255, 0);
    	// std::cout << m3 << std::endl;
    
    	// 显示图像
    	imshow("m3-1", m3);
    
    	// 拷贝构造(浅拷贝)会改变原始值
    	Mat m4 = m3;
    	m4 = Scalar(0, 255, 255);
    	imshow("m3-2", m3);
    
    	// 拷贝构造(深拷贝)不会改变原始值
    	Mat m5 = m3.clone();
    	m5 = Scalar(0, 255, 0);
    	imshow("m3-3", m3);
    
    	// 
    	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    }
    
    
    void QuickDemo::pixelVisit(Mat& img)
    {
    	// 获取图像宽高通道数
    	int w = img.cols;
    	int h = img.rows;
    	int dims = img.channels();
    	
    	// ------------------------------ 基于数组遍历 ------------------------------
    	// 循环宽度
    	/*
    	for (int row = 0; row < w; row++)
    	{
    		// 循环高度
    		for (int col = 0; col < h; col++)
    		{
    			// 灰度图
    			if (dims == 1)
    			{
    				// 获取像素值
    				int pv = img.at<uchar>(row, col);
    				// 修改像素值
    				img.at<uchar>(row, col) = 255 - pv;
    			}
    			// 彩色图
    			else if (dims == 3)
    			{
    				// 获取像素值
    				Vec3b brg = img.at<Vec3b>(row, col);
    				// 修改像素值
    				img.at<Vec3b>(row, col)[0] = 255 - brg[0];
    				img.at<Vec3b>(row, col)[1] = 255 - brg[1];
    				img.at<Vec3b>(row, col)[2] = 255 - brg[2];
    			}
    		}
    	}
    	*/
    
    	// ------------------------------ 基于指针遍历 ------------------------------
    	// 循环宽度
    	for (int row = 0; row < w; row++)
    	{
    		uchar* current_row = img.ptr<uchar>(row);
    		// 循环高度
    		for (int col = 0; col < h; col++)
    		{
    			// 灰度图
    			if (dims == 1)
    			{
    				// 获取像素值
    				int pv = *current_row;
    				// 修改像素值
    				*current_row++ = 255 - pv;
    			}
    			// 彩色图
    			else if (dims == 3)
    			{
    				*current_row++ = 255 - *current_row;
    				*current_row++ = 255 - *current_row;
    				*current_row++ = 255 - *current_row;
    			}
    		}
    	}
    	// 显示图像
    	imshow("像素读写演示", img);
    }
    
  • 测试 图像像素的读写操作函数

  • 04 图像像素的读写操作.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/new_116.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 调用色彩空间转换函数
    	qd.pixelVisit(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

六、图像像素的算数操作

1、知识点

(1) add()

作用:计算两个数组或一个数组和一个标量的每个元素的总和。

语法:void add(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray(), int dtype = -1);

参数解释:

  • src1:输入图像1
  • src2:输入图像2
  • dst:输出图像
  • mask:掩码,可选操作掩码 - 8 位单通道数组,指定要更改的输出数组元素。
  • dtype:输出数组的可选深度

(2) subtract()

作用:计算两个数组或数组与标量之间的每个元素的差异。

语法:void subtract(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray(), int dtype = -1);

参数解释:

  • src1:输入图像1
  • src2:输入图像2
  • dst:输出图像
  • mask:掩码,可选操作掩码 - 8 位单通道数组,指定要更改的输出数组元素。
  • dtype:输出数组的可选深度

(3) multiply()

作用:该函数乘以计算两个数组的每个元素乘积:

语法:void multiply(InputArray src1, InputArray src2, OutputArray dst, double scale = 1, int dtype = -1);

参数解释:

  • src1:输入图像1
  • src2:输入图像2(必须与 src1 大小和类型相同)
  • dst:输出图像(输出数组的大小和类型与 src1 相同。)
  • scale:标量因子
  • dtype:输出阵列的可选深度

(4) divide()

作用:一个数组除以另一个数组

语法:void divide(InputArray src1, InputArray src2, OutputArray dst, double scale = 1, int dtype = -1);

参数解释:

  • src1:输入图像1
  • src2:输入图像2(必须与 src1 大小和类型相同)
  • dst:输出图像(输出数组的大小和类型与 src1 相同。)
  • scale:标量因子
  • dtype:输出阵列的可选深度;如果 -1,DST 将具有深度 src2.depth(),但在逐数组划分的情况下,您只能在 src1.depth()==src1.depth() 时传递 -2。

2、示例

  • 在头文件的 QuickDemo 中添加 void pixeOperators(Mat& img); 成员函数

    	// 4. 图像像素的算数操作
    	void pixeOperators(Mat& img);
    
  • 在源文件中 实现 void pixeOperators(Mat& img);

    void QuickDemo::pixeOperators(Mat& img)
    {
    	// ------------------------------ 自定义实现 ------------------------------
    	Mat dst;
    	dst = img + Scalar(50, 50, 50);
    	imshow("加法操作", dst);
    
    	dst = img - Scalar(50, 50, 50);
    	imshow("减法操作", dst);
    
    	dst = img / Scalar(2, 2, 2);
    	imshow("除法操作", dst);
    
    	Mat m = Mat::zeros(img.size(), img.type());
    	m = Scalar(2, 2, 2);
    	multiply(img, m, dst);
    	imshow("乘法操作", dst);
    
    	/*
    	// 获取图像宽高通道数
    	Mat a = Mat::zeros(img.size(), img.type());
    	Mat b = Mat::zeros(img.size(), img.type());
    	int w = img.cols;
    	int h = img.rows;
    	int dims = img.channels();
    	// 循环宽度
    	for (int row = 0; row < w; row++)
    	{
    		// 循环高度
    		for (int col = 0; col < h; col++)
    		{
    			// 获取像素值
    			Vec3b p1 = img.at<Vec3b>(row, col);
    			Vec3b p2 = m.at<Vec3b>(row, col);
    			// 修改像素值
    			a.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);
    			a.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);
    			a.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[3]);
    		}
    	}
    	imshow("a+b", a);
    	*/
    
    	// ------------------------------ 使用 opencv API ------------------------------
    	// 加法
    	m = Scalar(50, 50, 50);
    	add(img, m, dst);
    	imshow("OpenCV 加法操作", dst);
    
    	// 减法
    	m = Scalar(50, 50, 50);
    	subtract(img, m, dst);
    	imshow("OpenCV 减法操作", dst);
    
    	// 乘法
    	m = Scalar(2, 2, 2);
    	multiply(img, m, dst);
    	imshow("OpenCV 乘法操作", dst);
    
    	// 除法
    	m = Scalar(2, 2, 2);
    	divide(img, m, dst);
    	imshow("OpenCV 除法操作", dst);
    }
    
  • 测试 图像像素的算数操作函数

  • 05 图像像素的算数操作.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 图像像素的算数操作函数
    	qd.pixeOperators(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

3、TrackBar

(1) 调整图像亮度

  • 在头文件的 QuickDemo 中添加 void trackBar(Mat& img); 成员函数

    	// 5. TrackBar - 滚动条操作演示
    	void trackBar(Mat& img);
    
  • 在源文件中 实现 void trackBar(Mat& img);

    Mat src, dst, m;
    int lightness = 50;
    static void on_track(int, void*)
    {
    	m = Scalar(lightness, lightness, lightness);
    	add(src, m, dst);
    	// subtract(src, m, dst);
    	imshow("亮度调整", dst);
    }
    
    void QuickDemo::trackBar(Mat& img)
    {
    	namedWindow("亮度调整", WINDOW_AUTOSIZE);
    	src = img;
    	dst = Mat::zeros(img.size(), img.type());
    	m = Mat::zeros(img.size(), img.type());
    	int max_value = 100;
    	lightness = 50;
    	createTrackbar("Value Bar: ", "亮度调整", &lightness, max_value, on_track);
    	on_track(50, 0);
    }
    
  • 测试 TrackBar - 滚动条操作演示函数

  • 06 TrackBar- 滚动条操作演示.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 图像像素的算数操作函数
    	qd.trackBar(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

(2) 参数传递与调整亮度与对比度

  • 在头文件的 QuickDemo 中添加 void trackBar(Mat& img); 成员函数

    	// 5. TrackBar - 滚动条操作演示
    	void trackBar(Mat& img);
    
  • 在源文件中 实现 void trackBar(Mat& img);

    static void on_lightness(int b, void* userdata)
    {
    	Mat img = *((Mat*)userdata);
    	Mat dst = Mat::zeros(img.size(), img.type());
    	Mat m = Mat::zeros(img.size(), img.type());
    	addWeighted(img, 1.0, m, 0, b, dst);
    	imshow("亮度与对比度调整", dst);
    }
    
    static void on_contrast(int b, void* userdata)
    {
    	Mat img = *((Mat*)userdata);
    	Mat dst = Mat::zeros(img.size(), img.type());
    	Mat m = Mat::zeros(img.size(), img.type());
    	double contrast = b / 100.0;
    	addWeighted(img, contrast, m, 0.0, b, dst);
    	imshow("亮度与对比度调整", dst);
    }
    
    void QuickDemo::trackBar(Mat& img)
    {
    	namedWindow("亮度与对比度调整", WINDOW_AUTOSIZE);
    	int max_value = 100;
    	int lightness = 50;
    	int contrast_value = 100;
    	int max_contrast_value = 200;
    	createTrackbar("Value Bar: ", "亮度与对比度调整", &lightness, max_value, on_lightness, (void*) (&img));
    	createTrackbar("Contrast Bar: ", "亮度与对比度调整", &contrast_value, max_contrast_value, on_contrast, (void*) (&img));
    	on_lightness(50, &img);
    }
    
  • 测试 TrackBar - 滚动条操作演示函数

  • 06 TrackBar- 滚动条操作演示.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 图像像素的算数操作函数
    	qd.trackBar(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

4、键盘响应操作

  • 在头文件的 QuickDemo 中添加 void keyboardOperation(Mat& img); 成员函数

    	// 6. 键盘响应操作
    	void keyboardOperation(Mat& img);
    
  • 在源文件中 实现 void keyboardOperation(Mat& img);

    void QuickDemo::keyboardOperation(Mat& img)
    {
    	Mat dst = Mat::zeros(img.size(), img.type());
    	Mat m = Mat::zeros(img.size(), img.type());
    	/*
    	while (true)
    	{
    		int c = waitKey(10);
    		if (c == 27) // ESC
    		{
    			break;
    		}
    		else if (c == 49) // 1
    		{
    			cout << "you enter key # 1" << endl;
    			cvtColor(img, dst, COLOR_BGR2GRAY);
    		}
    		else if (c == 50) // 2
    		{
    			cout << "you enter key # 2" << endl;
    			cvtColor(img, dst, COLOR_BGR2HSV);
    		}
    		else if (c == 51) // 3
    		{
    			cout << "you enter key # 3" << endl;
    			m = Scalar(50, 50, 50);
    			add(img, m, dst);
    		}
    		imshow("键盘响应", dst);
    	}
    	*/
    	bool flag = true;
    	while (flag)
    	{
    		int c = waitKey(10);
    		switch (c)
    		{
    		case 27:
    			flag = false;
    			break;
    		case 49:
    			cout << "you enter key # 1" << endl;
    			cvtColor(img, dst, COLOR_BGR2GRAY);
    			break;
    		case 50:
    			cout << "you enter key # 2" << endl;
    			cvtColor(img, dst, COLOR_BGR2HSV);
    			break;
    		case 51:
    			cout << "you enter key # 3" << endl;
    			m = Scalar(50, 50, 50);
    			add(img, m, dst);
    			break;
    		default:
    			break;
    		}
    		imshow("键盘响应", dst);
    	}
    }
    
  • 测试 键盘响应操作函数

  • 07 键盘响应操作.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 键盘响应操作函数
    	qd.keyboardOperation(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

七、OpenCV 自带颜色表操作

1、知识点:

(1) applyColorMap

作用:更改图像颜色样式

语法:void applyColorMap(InputArray src, OutputArray dst, int colormap);

参数解释:

  • src:输入图像(矩阵)
  • dst:输出图像(矩阵)
  • colormap:颜色样式代码

2、示例

  • 在头文件的 QuickDemo 中添加 void colorStyle(Mat& img); 成员函数

    	// 7. OpenCV 自带颜色表操作
    	void colorStyle(Mat& img);
    
  • 在源文件中 实现 void keyboardOperation(Mat& img);

    void QuickDemo::colorStyle(Mat& img)
    {
    	int colorMap[] = {
    	  COLORMAP_AUTUMN ,
    	  COLORMAP_BONE ,
    	  COLORMAP_JET ,
    	  COLORMAP_WINTER ,
    	  COLORMAP_RAINBOW ,
    	  COLORMAP_OCEAN ,
    	  COLORMAP_SUMMER ,
    	  COLORMAP_SPRING ,
    	  COLORMAP_COOL ,
    	  COLORMAP_HSV ,
    	  COLORMAP_PINK ,
    	  COLORMAP_HOT ,
    	  COLORMAP_PARULA,
    	  COLORMAP_MAGMA,
    	  COLORMAP_INFERNO,
    	  COLORMAP_PLASMA,
    	  COLORMAP_VIRIDIS,
    	  COLORMAP_CIVIDIS,
    	  COLORMAP_TWILIGHT,
    	  COLORMAP_TWILIGHT_SHIFTED,
    	  COLORMAP_TURBO,
    	  COLORMAP_DEEPGREEN
    	};
    	Mat dst = Mat::zeros(img.size(), img.type());
    	int index = 0;
    	while (true)
    	{
    		int c = waitKey(2000);
    		if (c == 27)
    		{
    			break;
    		}
    		cout << "颜色风格: " << index % 19 << endl;
    		applyColorMap(img, dst, colorMap[index % 19]);
    		index++;
    		imshow("颜色风格", dst);
    	}
    }
    
  • 测试 OpenCV 自带颜色表操作函数

  • 08 自带颜色表操作.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	imshow("输入窗口", src);
    
    	// 4. 自带颜色表操作函数
    	qd.colorStyle(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	system("pause");
    	return 0;
    }
    
  • 测试结果

八、图像像素的逻辑操作

1、知识点

(1) bitwise_and:

作用:

语法:void bitwise_and(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray());

参数解释:

  • src1:
  • src2:
  • dst:
  • mask:

(2) bitwise_xor:

作用:

语法:void bitwise_xor(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray());

参数解释:

  • src1:
  • src2:
  • dst:
  • mask:

(3) bitwise_or:

作用:

语法:void bitwise_or(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray());

参数解释:

  • src1:
  • src2:
  • dst:
  • mask:

(4) bitwise_not:

作用:针对输入图像,图像取反操作,二值图像分析中经常使用

语法:void bitwise_not(InputArray src, OutputArray dst, InputArray mask = noArray());

参数解释:

  • src:
  • dst:
  • mask:

2、示例

  • 在头文件的 QuickDemo 中添加 void bitwiseOperation(Mat& img); 成员函数

    	// 8. 图像像素的逻辑操作
    	void bitwiseOperation(Mat& img);
    
  • 在源文件中 实现 void bitwiseOperation(Mat& img);

    void QuickDemo::bitwiseOperation(Mat& img)
    {
        // 改变大小
    	Mat image;
    	resize(img, image, Size(256, 256), 0, 0, INTER_AREA);
    
    	Mat m1 = Mat::zeros(Size(256, 256), CV_8UC3);
    	Mat m2 = Mat::zeros(Size(256, 256), CV_8UC3);
    	/*
    	绘制矩形
    	rectangle(
    		InputOutputArray img, 
    		Rect rec, 
    		const Scalar& color, 
    		int thickness = 1, 
    		int lineType = LINE_8, 
    		int shift = 0
    	);
    		- img: 输入图像
    		- rec: 矩形框位置与大小 Rect 参数1:左上角 X轴坐标; 参数2:左上角 Y轴坐标; 参数3:宽度; 参数4:高度;
    		- color: 矩形框颜色 Scalar BGR
    		- thickness: 填充样式 -1: 填充; 2: 线宽; 
    		- lineType: 线样式 [LINE_4: , LINE_8: , LINE_AA: 抗锯齿]
    		- shift: 转变
    	*/
    	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), -2, LINE_8, 0);
    	imshow("m1", m1);
    	imshow("m2", m2);
    
    	// 位操作 - 与操作
    	Mat dst;
    	bitwise_and(m1, m2, dst);
    	imshow("bitwise_and", dst);
    
    	// 位操作 - 或操作
    	bitwise_or(m1, m2, dst);
    	imshow("bitwise_or", dst);
    
    	// 位操作 - 反向操作
    	// dst = ~image;
    	bitwise_not(image, dst);
    	imshow("bitwise_not", dst);
    
    	// 位操作 - 异或操作
    	bitwise_xor(m1, m2, dst);
    	imshow("bitwise_xor", dst);
    }
    
  • 测试 图像像素的逻辑操作函数

  • 09 图像像素的逻辑操作.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像像素的逻辑操作
    	qd.bitwiseOperation(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

九、图像通道分离与合并

1、知识点:

  • 通道分离与合并

  • OpenCVi中默认imreadi函数加载图像文件,加载进来的是三通道彩色图像,色彩空间是RGB色彩空间、通道顺序是BGR(蓝色、绿色、红色)、对于三通道的图像OpenCV中提供了两个API函数用以实现通道分离与合并。

    • split:通道分类
    • merge:通道合并
  • 在很多CNN的卷积神经网络中输入的图像一般会要求[h,w,ch]其中h是高度、w是指宽度、ch是指通道数数目、OpenCV DNN模块中关于图像分类的googlenet模型输入[224,224,3]表示的就是224x224大小的三通道的彩色图像输入。

2、示例

  • 在头文件的 QuickDemo 中添加 void channelsOperation(Mat& img); 成员函数

    	// 9. 图像通道分离与合并
    	void channelsOperation(Mat& img);
    
  • 在源文件中 实现 void channelsOperation(Mat& img);

    void QuickDemo::channelsOperation(Mat& img)
    {
    	resize(img, img, Size(256, 256), 0, 0, INTER_AREA);
    	imshow("原图", img);
    	Mat dst;
    
    	// B
    	vector<Mat> mv1;
    	split(img, mv1);
    	mv1[1] = 0;
    	mv1[2] = 0;
    	merge(mv1, dst);
    	imshow("B", dst);
    
    	// G
    	vector<Mat> mv2;
    	split(img, mv2);
    	mv2[0] = 0;
    	mv2[2] = 0;
    	merge(mv2, dst);
    	imshow("G", dst);
    
    	// R
    	vector<Mat> mv3;
    	split(img, mv3);
    	mv3[0] = 0;
    	mv3[1] = 0;
    	merge(mv3, dst);
    	imshow("R", dst);
    
    	// 通道混合
    	int from_to[] = { 0, 2, 1, 1, 2, 0 };
    	/*
    	void mixChannels(const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs);
    		- src: 原图的引用
    		- nsrcs: 
    		- dst: 目标图的引用
    		- ndsts:
    		- fromTo:
    		- npairs: 
    	*/
    	mixChannels(&img, 1, &dst, 1, from_to, 3);
    	imshow("通道混合", dst);
    }
    
  • 测试 图像通道分离与合并函数

  • 10 图像通道分离与合并.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像通道分离与合并
    	qd.channelsOperation(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十、图像色彩空间转换

1、知识点

  • 色彩空间与色彩空间转换
    • RGB色彩空间
    • HSV色彩空间
    • YUV色彩空间
    • YCrCb色彩空间
  • API知识点
    • 色彩空间转换cvtColor
    • 提取指定色彩范围区域inRange

2、示例

  • 在头文件的 QuickDemo 中添加 void inRangeConversion(Mat& img); 成员函数

    	// 10. 图像色彩空间转换
    	void inRangeConversion(Mat& img);
    
  • 在源文件中 实现 void inRangeConversion(Mat& img);

    void QuickDemo::inRangeConversion(Mat& img)
    {
    	imshow("原图", img);
    	Mat hsv;
    	cvtColor(img, hsv, COLOR_BGR2HSV);
    	Mat mask;
    	inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), mask);
    	imshow("mask1", mask);
    
    	Mat redback = Mat::zeros(img.size(), img.type());
    	redback = Scalar(40, 40, 200);
    	bitwise_not(mask, mask);
    	imshow("mask2", mask);
    	img.copyTo(redback, mask);
    	imshow("roi区域提取", redback);
    }
    
  • 测试 图像色彩空间转换函数

  • 11 图像色彩空间转换.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_02.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像色彩空间转换
    	qd.inRangeConversion(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十一、图像像素值统计

1、知识点

像素值统计

  • 最小(min)
  • 最大(max)
  • 均值(mean)
  • 标准方差(standard deviation)

API知识点

  • 最大最小值minMaxLoc
  • 计算均值与标准方差meanStdDev

(1) minMaxLoc

作用:寻找最小/最大值

语法:

void minMaxLoc(InputArray src, CV_OUT double* minVal,
               CV_OUT double* maxVal = 0, CV_OUT Point* minLoc = 0,
               CV_OUT Point* maxLoc = 0, InputArray mask = noArray());

参数解释:

  • src:输入
  • minVal:输出的最小值
  • maxVal:输出的最大值
  • minLoc:最小值下标
  • maxLoc:最大值下标
  • mask:可选mask

2、示例

  • 在头文件的 QuickDemo 中添加 void pixelStatistic(Mat& img); 成员函数

    	// 11. 图像像素值统计
    	void pixelStatistic(Mat& img);
    
  • 在源文件中 实现 void pixelStatistic(Mat& img);

    void QuickDemo::pixelStatistic(Mat& img)
    {
    	
    	double minv, maxv;
    	Point minLoc, maxLoc;
    	vector<Mat> mv;
    	split(img, mv);
    	for (int i = 0; i < mv.size(); i++)
    	{
    		minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat());
    		std::cout << "No.channels:" << i << " 最小值: " << minv << " 最大值: " << maxv << std::endl;
    	}
    	Mat mean, stddev;
    	meanStdDev(img, mean, stddev);
    	std::cout << mean.size() << std::endl;
    	for (int i = 0; i < 3; i++)
    	{
    		std::cout << mean.at<double>(i, 0) << std::endl;
    	}
    	std::cout << "mean: " << mean << std::endl;
    	std::cout << " stddev: " << stddev << std::endl;
    }
    
  • 测试 图像像素值统计函数

  • 12 图像像素值统计.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/demo_01.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像像素值统计函数
    	qd.pixelStatistic(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十二、几何形状绘制

1、知识点

(1) rectangle

语法:

void rectangle(InputOutputArray img, Rect rec, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

参数解释:

  • img:表示绘制画布,图像。
  • rec:矩形左上角定点和长宽。
  • color:颜色 BGR。
  • thickness:线宽。
  • lineType:线型。
  • shift:坐标点的小数点位。

示例:

// 绘制矩形
// 方式一:
Rect rect;
rect.x = 270;
rect.y = 70;
rect.width = 120;
rect.height = 120;
rectangle(img, rect, Scalar(0, 0, 255), 2, LINE_8, 0);
// 方式二
rectangle(img, Rect(270, 70, 120, 120), Scalar(0, 0, 255), 2, LINE_8, 0);

(2) circle

语法:

void circle(InputOutputArray img, Point center,int radius, const Scalar& color, int thickness = 1,int lineType = LINE_8, int shift = 0);

参数解释:

  • img:表示绘制画布,图像。
  • center:圆形的圆心位置坐标。
  • radius:圆形的半径长度,单位为像素。
  • color:颜色 BGR。
  • thickness:轮廓的宽度,如果数值为负,则绘制一个实心圆。
  • lineType:边界的类型,可取值为FILLED ,LINE_4 ,LINE_8 和LINE_AA
  • shift:中心坐标和半径数值中的小数位数。

示例:

// 绘制圆
circle(img, Point(330, 130), 60, Scalar(255, 0, 0), -1, LINE_8, 0);

(3) line

语法:

void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

参数解释:

  • img:表示绘制画布,图像。
  • pt1:直线起始点在图像中的坐标。
  • pt2:直线终点在图像中的坐标。
  • color:颜色 BGR。
  • thickness:线宽。
  • lineType:线型。
  • shift:坐标点的小数点位。

示例:

// 绘制圆
circle(img, Point(330, 130), 60, Scalar(255, 0, 0), -1, LINE_8, 0);

(4) ellipse

语法:

void ellipse(InputOutputArray img, const RotatedRect& box, const Scalar& color, int thickness = 1, int lineType = LINE_8);

参数解释:

  • img:表示绘制画布,图像。
  • box:box可选椭圆表示通过RotatedRect。这意味着函数进行绘图 椭圆:刻在旋转矩形中的椭圆。
  • color:颜色 BGR。
  • thickness:线宽。
  • lineType:线型。

示例:

// 绘制椭圆
RotatedRect rrt;
rrt.center = Point(330, 130);
rrt.size = Size(60, 120);
rrt.angle = 90.0;
ellipse(img, rrt, Scalar(255, 255, 0), 2, 8);

(5) polylines

语法:

polylines(
    InputOutputArray img, 
    InputArrayOfArrays pts,
    bool isClosed, 
    const Scalar& color,
    int thickness = 1, 
    int lineType = LINE_8, 
    int shift = 0 
);

参数解释:

  • img:表示绘制画布,图像。
  • pts:表示多边形的点。
  • isClosed:表示是否闭合,默认闭合。
  • color:颜色 BGR
  • thickness:表示线宽,必须是正数。
  • lineType:表示线渲染类型。
  • shift:表示相对位移

示例:

// 绘制多边形
vector<Point> pts;
pts.push_back(Point(30, 30));
pts.push_back(Point(30, 130));
pts.push_back(Point(130, 130));
pts.push_back(Point(160, 100));
pts.push_back(Point(100, 30));
polylines(bg, pts, true, Scalar(255, 0, 255), 2, 8, 0);

(6) fillPoly

语法:

void fillPoly(
    InputOutputArray img, 
    InputArrayOfArrays pts,
    const Scalar& color, 
    int lineType = LINE_8, 
    int shift = 0,
    Point offset = Point() 
);

参数解释:

  • img:表示绘制画布,图像。
  • pts:多边形顶点数组,可以存放多个多边形的顶点坐标的数组。
  • color:颜色 BGR
  • lineType :表示线渲染类型。
  • shift :表示相对位移。
  • offset:所有顶点的可选偏移

示例:

// 绘制多边形
vector<Point> pts;
pts.push_back(Point(30, 230));
pts.push_back(Point(30, 330));
pts.push_back(Point(130, 330));
pts.push_back(Point(160, 300));
pts.push_back(Point(100, 230));
fillPoly(img, pts, Scalar(255, 0, 255), 8, 0);

(7) drawContours

语法:

void drawContours( 
    InputOutputArray image, 
    InputArrayOfArrays contours,
    int contourIdx, 
    const Scalar& color,
    int thickness = 1, 
    int lineType = LINE_8,
    InputArray hierarchy = noArray(),
    int maxLevel = INT_MAX, 
    Point offset = Point() 
);

参数解释:

  • image:表示绘制画布,图像。
  • contours:多边形集合。
  • contourIdx: 大于零表示绘制指定索引的轮廓,-1表示绘制全部。
  • color:颜色 BGR。
  • thickness:thickness正数表示绘制,非正数表示填充。
  • lineType :表示线渲染类型。
  • hierarchy:
  • maxLevel:
  • offset:

示例:

// 绘制多个多边形
std::vector<std::vector<Point>> contours;  //多边形集合
vector<Point> pts3;
pts3.push_back(Point(430, 30));
pts3.push_back(Point(430, 130));
pts3.push_back(Point(530, 130));
pts3.push_back(Point(560, 100));
pts3.push_back(Point(500, 30));
vector<Point> pts4;
pts4.push_back(Point(430, 230));
pts4.push_back(Point(430, 330));
pts4.push_back(Point(530, 330));
pts4.push_back(Point(560, 300));
pts4.push_back(Point(500, 230));
contours.push_back(pts3);
contours.push_back(pts4);
drawContours(bg, contours, -1, Scalar(0, 255, 255), -1);

2、示例

  • 在头文件的 QuickDemo 中添加 void geometricFigureDraw(Mat& img); 成员函数

    	// 12. 几何形状绘制
    	void geometricFigureDraw(Mat& img);
    
  • 在源文件中 实现 void geometricFigureDraw(Mat& img);

    void QuickDemo::geometricFigureDraw(Mat& img)
    {
    	imshow("原图", img);
    	Mat dst;
    	Mat bg = Mat::zeros(img.size(), img.type());
    	Rect rect;
    	// 定义矩形的坐标和宽高
    	rect.x = 270;
    	rect.y = 70;
    	rect.width = 120;
    	rect.height = 120;
    	// 1. 绘制矩形
    	rectangle(bg, rect, Scalar(0, 0, 255), 2, LINE_8, 0);
    
    	// 2. 绘制圆
    	circle(bg, Point(330, 130), 60, Scalar(255, 0, 0), -1, LINE_8, 0);
    	
    	// 3. 绘制线
    	line(bg, Point(270, 130), Point(390, 130), Scalar(0, 255, 0), 2, LINE_8, 0);
    
    	// 4. 绘制椭圆
    	RotatedRect rrt;
    	rrt.center = Point(330, 130);
    	rrt.size = Size(60, 120);
    	rrt.angle = 90.0;
    	ellipse(bg, rrt, Scalar(255, 255, 0), 2, 8);
    
    	// 5. 绘制多边形
    	vector<Point> pts1;
    	pts1.push_back(Point(30, 30));
    	pts1.push_back(Point(30, 130));
    	pts1.push_back(Point(130, 130));
    	pts1.push_back(Point(160, 100));
    	pts1.push_back(Point(100, 30));
    	polylines(bg, pts1, true, Scalar(255, 0, 255), 2, 8, 0);
    
    	// 6. 填充多边形
    	vector<Point> pts2;
    	pts2.push_back(Point(30, 230));
    	pts2.push_back(Point(30, 330));
    	pts2.push_back(Point(130, 330));
    	pts2.push_back(Point(160, 300));
    	pts2.push_back(Point(100, 230));
    	fillPoly(bg, pts2, Scalar(255, 0, 255), 8, 0);
    
    	// 7. 绘制多个多边形
    	std::vector<std::vector<Point>> contours;  //多边形集合
    	vector<Point> pts3;
    	pts3.push_back(Point(430, 30));
    	pts3.push_back(Point(430, 130));
    	pts3.push_back(Point(530, 130));
    	pts3.push_back(Point(560, 100));
    	pts3.push_back(Point(500, 30));
    	vector<Point> pts4;
    	pts4.push_back(Point(430, 230));
    	pts4.push_back(Point(430, 330));
    	pts4.push_back(Point(530, 330));
    	pts4.push_back(Point(560, 300));
    	pts4.push_back(Point(500, 230));
    	contours.push_back(pts3);
    	contours.push_back(pts4);
    	drawContours(bg, contours, -1, Scalar(0, 255, 255), -1);
    
    	// 图像叠加
    	addWeighted(img, 0.7, bg, 0.3, 0, dst);
    	imshow("绘制演示", dst);
    }
    
  • 测试 几何形状绘制函数

  • 13 几何形状绘制.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 几何形状绘制
    	qd.geometricFigureDraw(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十三、随机数与随机颜色

1、知识点

RNG类是opencv里C++的随机数产生器。它可产生一个64位的int随机数。目前可按均匀分布和高斯分布产生随机数。随机数的产生采用的是Multiply-With-Carry算法和Ziggurat算法。

(1) 产生一个随机数

RNG 可以产生3种随机数

  • RNG(int seed) 使用种子seed产生一个64位随机整数,默认-1

  • RNG::uniform( ) 产生一个均匀分布的随机数

  • RNG::gaussian( ) 产生一个高斯分布的随机数

注:
RNG::uniform(a, b ) 返回一个[a,b)范围的均匀分布的随机数,a,b的数据类型要一致,而且必须是int、float、double中的一种,默认是int。
RNG::gaussian( σ) 返回一个均值为0,标准差为σ的随机数。 如果要产生均值为λ,标准差为σ的随机数,可以λ+ RNG::gaussian( σ)

//创建RNG对象,使用默认种子“-1”  
RNG rng; 

//产生64位整数  
int N1 = rng; 

/*-------------产生均匀分布的随机数uniform和高斯分布的随机数gaussian---------*/  
//总是得到double类型数据0.000000,因为会调用uniform(int,int),只会取整数,所以只产生0   
double N1a = rng.uniform(0,1);  

//产生[0,1)范围内均匀分布的double类型数据  
double N1b = rng.uniform((double)0,(double)1); 

//产生[0,1)范围内均匀分布的float类型数据,注意被自动转换为double了。  
double N1c = rng.uniform(0.f,1.f); 

//产生[0,1)范围内均匀分布的double类型数据。  
double N1d = rng.uniform(0.,1.); 

//可能会因为重载导致编译不通过(确实没通过。。)     
//double N1e = rng.uniform(0,0.999999); 

//产生符合均值为0,标准差为2的高斯分布的随机数  
double N1g = rng.gaussian(2); 

注: rng既是一个RNG对象,也是一个随机整数。

(2) 返回下一个随机数

上面一次只能返回一个随机数,实际上系统已经生成一个随机数组。如果我们要连续获得随机数,没有必要重新定义一个RNG类,只需要取出随机数组的下一个随机数即可。

RNG:: next			// 返回下一个64位随机整数 
RNG:: operator		// 返回下一个指定类型的随机数
RNG rng;
int N2 = rng.next();                    //返回下一个随机整数,即N1.next(); 
//返回下一个指定类型的随机数
int N2a = rng.operator uchar();         //返回下一个无符号字符数
int N2b = rng.operator schar();         //返回下一个有符号字符数
int N2c = rng.operator ushort();        //返回下一个无符号短型
int N2d = rng.operator short int();     //返回下一个短整型数
int N2e = rng.operator int();           //返回下一个整型数 
int N2f = rng.operator unsigned int();  //返回下一个无符号整型数
int N2g = rng.operator float();         //返回下一个浮点数 
int N2h = rng.operator double();        //返回下一个double型数
int N2i = rng.operator ()();            //和rng.next( )等价
int N2j = rng.operator ()(100);         //返回[0,100)范围内的随机数 

(3) 用随机数填充矩阵 RNG::fill( )

语法:void fill( InputOutputArray mat, int distType, InputArray a, InputArray b, bool saturateRange=false );

参数说明:

  • mat: 输入输出矩阵,最多支持4通道,超过4通道先用reshape()改变结构
  • distType:UNIFORM 或 NORMAL,表示均匀分布和高斯分布
  • a:disType是UNIFORM,a表示为下界(闭区间);disType是NORMAL,a均值
  • b:disType是UNIFORM,b表示为上界(开区间);disType是NORMAL,b标准差
  • saturateRange:只针对均匀分布有效。当为真的时候,会先把产生随机数的范围变换到数据类型的范围,再产生随机数;如果为假,会先产生随机数,再进行截断到数据类型的有效区间。请看以下fillM1和fillM2的例子并观察结果
//产生[1,1000)均匀分布的int随机数填充fillM  
Mat_<int>fillM(3,3);
rng.fill(fillM,RNG::UNIFORM,1,1000);  

Mat fillM1(3,3,CV_8U); 
rng.fill(fillM1,RNG::UNIFORM,1,1000,TRUE); 

Mat fillM2(3,3,CV_8U); 
rng.fill(fillM2,RNG::UNIFORM,1,1000,FALSE); 

//fillM1产生的数据都在[0,,255)内,且小于255;
//fillM2产生的数据虽然也在同样范围内,但是由于用了截断操作,所以很多数据都是255,  

//产生均值为1,标准差为3的随机double数填进fillN  
Mat_<double>fillN(3,3); 
rng.fill(fillN,RNG::NORMAL,1,3);

(4) RNG& rng=theRNG();

RNG& rng=theRNG();
double x= (double)rng;
float y= (float)rng;
int z= (int)rng;

(5) 产生不重复的随机数

采取另一种方式,用系统时间作为种子初始化 rng

RNG rng((unsigned)time(NULL));

开始生成随机数

double x=rng.uniform((double)0,(double)255);
float y=rng.uniform(0.f,255.f);
int z=rng.uniform((int)0, (int)255 );

(6) randShuffle( ) 将原数组(矩阵)打乱

randShuffle
( 
    // 输入输出数组(一维)
	InputOutputArray dst,     
    // 决定交换数值的行列的位置的一个系数...
    double iterFactor=1. ,   
    // (可选)随机数产生器,0表示使用默认的随机数产生器,即seed=-1。rng决定了打乱的方法
    RNG* rng=0               
)

2、示例

  • 在头文件的 QuickDemo 中添加 void randomDraw(Mat& img); 成员函数

    	// 13. 随机数与随机颜色
    	void randomDraw(Mat& img);
    
  • 在源文件中 实现 void randomDraw(Mat& img);

    void QuickDemo::randomDraw(Mat& img)
    {
    	// 创建一张空白图像
    	Mat canvas = Mat::zeros(Size(512, 512), CV_8UC3);
    	// 获取图像宽高
    	int w = canvas.cols;
    	int h = canvas.rows;
    	// 使用种子seed产生一个64位随机整数,默认-1
    	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);
    	}
    }
    
  • 测试 随机数与随机颜色函数

  • 14 随机数与随机颜色.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 几何形状绘制
    	qd.randomDraw(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十四、鼠标操作与响应

1、知识点

(1) setMouseCallback

作用:监听鼠标操作

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

参数解释:

  • winname:窗口名称
  • onMouse:回调函数
  • userdata:图像信息

回调函数:

  • 语法:typedef void(*cv::MouseCallback) (int event, int x, int, y, int flags, void* userdata)

  • 参数解释:

    • event:
      • EVENT_MOUSEMOVE:指示鼠标指针已移到窗口上。
      • EVENT_LBUTTONDOWN:表示按下鼠标左键。
      • EVENT_RBUTTONDOWN:表示按下鼠标右键。
      • EVENT_MBUTTONDOWN:表示按下鼠标中键。
      • EVENT_LBUTTONUP:表示释放鼠标左键。
      • EVENT_RBUTTONUP:表示释放鼠标右键。
      • EVENT_MBUTTONUP:表示释放鼠标中键。
      • EVENT_LBUTTONDBLCLK:表示双击鼠标左键。
      • EVENT_RBUTTONDBLCLK:表示双击鼠标右键。
      • EVENT_MBUTTONDBLCLK:表示双击鼠标中键。
      • EVENT_MOUSEWHEEL:正值和负值分别表示向前和向后滚动。
      • EVENT_MOUSEHWHEEL:正值和负值分别表示向右和向左滚动。
    • x:坐标X
    • y:坐标Y
    • flags:
      • EVENT_FLAG_LBUTTON:表示鼠标左键已关闭。
      • EVENT_FLAG_RBUTTON:表示鼠标右键已关闭。
      • EVENT_FLAG_MBUTTON:表示鼠标中键已关闭。
      • EVENT_FLAG_CTRLKEY:表示按下 CTRL 键。
      • EVENT_FLAG_SHIFTKEY:表示按下 SHIFT 键。
      • EVENT_FLAG_ALTKEY:表示按下 ALT 键。
    • userdata:图像信息

2、示例

  • 在头文件的 QuickDemo 中添加 void mouseOperation(Mat& img); 成员函数

    	// 14. 鼠标操作与响应
    	void mouseOperation(Mat& img);
    
  • 在源文件中 实现 void mouseOperation(Mat& img);

    map<string, Point> point_item;
    Mat temp;
    Rect box;
    static void on_mouse(int event, int x, int y, int flags, void* userdata)
    {
    	Mat image = *((Mat*)userdata);
    	// 判断鼠标事件
    	// 1. 按下鼠标左键
    	if (event == EVENT_LBUTTONDOWN)
    	{
    		point_item.insert(make_pair("start_point", Point(x, y)));
    		std::cout << "开始坐标: " << point_item["start_point"] << std::endl;
    	}
    	// 2. 鼠标移动事件
    	else if (event == EVENT_MOUSEMOVE)
    	{
    		// 更新结束坐标
    		map<string, Point>::iterator pos = point_item.find("end_point");
    		if (pos != point_item.end())
    		{
    			point_item["end_point"] = Point(x, y);
    		}
    		else
    		{
    			point_item.insert(make_pair("end_point", Point(x, y)));
    		}
    	}
    	// 3. 释放鼠标左键
    	else if (event == EVENT_LBUTTONUP)
    	{
    		point_item["end_point"] = Point(x, y);
    		std::cout << "结束坐标: " << point_item["end_point"] << std::endl;
    		temp.copyTo(image);
    		imshow("ROI", image(box));
    		point_item.erase("start_point");
    		point_item.erase("end_point");
    		
    	}
    	// 4. 画出矩形框
    	map<string, Point>::iterator startr_pos = point_item.find("start_point");
    	map<string, Point>::iterator end_pos = point_item.find("end_point");
    	if (startr_pos != point_item.end() && end_pos != point_item.end())
    	{
    		Point start_point = point_item["start_point"];
    		Point end_point = point_item["end_point"];
    		box = Rect(start_point.x, start_point.y, end_point.x - start_point.x, end_point.y - start_point.y);
    		//在图像上画出矩形
    		temp.copyTo(image);
    		rectangle(image, box, Scalar(0, 0, 250), 2, 4);
    		imshow("鼠标绘制", image);
    	}
    }
    
    void QuickDemo::mouseOperation(Mat& img)
    {
    	namedWindow("鼠标绘制", WINDOW_AUTOSIZE);
    	setMouseCallback("鼠标绘制", on_mouse, (void*)(&img));
    	imshow("鼠标绘制", img);
    	temp = img.clone();
    }
    
  • 测试 鼠标操作与响应函数

  • 15 鼠标操作与响应.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 鼠标操作与响应
    	qd.mouseOperation(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十五、图像像素类型转换与归一化

1、知识点

(1) normalize

作用:像素归一化

语法:

    void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0,
                    int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());

参数解释:

  • src:输入数组。
  • dst:输出数组的大小与 src 相同。
  • alpha:规范值,用于规范化到或范围规范化情况下的下限范围边界。
  • beta:在范围归一化的情况下,上限范围边界; 它不用于规范化。
  • norm_type:
    • NORM_INF:
    • NORM_L1:
    • NORM_L2:
    • NORM_L2SQR:
    • NORM_HAMMING:
    • NORM_HAMMING2:
    • NORM_TYPE_MASK:
    • NORM_RELATIVE:
    • NORM_MINMAX:
  • dtype:当为负时,输出数组与 SRC 的类型相同;否则,它具有与SRC相同数量的通道,深度=CV_MAT_DEPTH(dtype)。
  • mask:可选操作掩码。

官方示例:

vector<double> positiveData = { 2.0, 8.0, 10.0 };
vector<double> normalizedData_l1, normalizedData_l2, normalizedData_inf, normalizedData_minmax;
// Norm to probability (total count) 求相对总数的概率
// sum(numbers) = 20.0
// 2.0      0.1     (2.0/20.0)
// 8.0      0.4     (8.0/20.0)
// 10.0     0.5     (10.0/20.0)
// 概率和为 1,即 0.1+0.4+0.5=1
normalize(positiveData, normalizedData_l1, 1.0, 0.0, NORM_L1);
// Norm to unit vector: ||positiveData|| = 1.0
// 2.0      0.15
// 8.0      0.62
// 10.0     0.77
// 归一化后的平方和为 1,即 0.15^2+0.62^2+0.77^2=1
normalize(positiveData, normalizedData_l2, 1.0, 0.0, NORM_L2);
// Norm to max element 除以最大值
// 2.0      0.2     (2.0/10.0)
// 8.0      0.8     (8.0/10.0)
// 10.0     1.0     (10.0/10.0)
normalize(positiveData, normalizedData_inf, 1.0, 0.0, NORM_INF);
// Norm to range [0.0;1.0] 减去最小值,再除以(最大值-最小值)
// 2.0      0.0     (shift to left border)
// 8.0      0.75    (6.0/8.0)
// 10.0     1.0     (shift to right border)
normalize(positiveData, normalizedData_minmax, 1.0, 0.0, NORM_MINMAX);

(2) convertTo

作用:缩放并转换到另外一种数据类型

语法:src.convertTo(dst, type, scale, shift)

参数解释:

  • dst:目的矩阵
  • type:需要的输出矩阵类型,或者更明确的,是输出矩阵的深度,如果是负值(常用-1)则输出矩阵和输入矩阵类型相同;
  • scale
  • shift

2、示例

  • 在头文件的 QuickDemo 中添加 void normalizeOperation(Mat& img); 成员函数

    	// 15. 图像像素类型转换与归一化
    	void normalizeOperation(Mat& img);
    
  • 在源文件中 实现 void normalizeOperation(Mat& img);

    void QuickDemo::normalizeOperation(Mat& img)
    {
    	imshow("原图", img);
    	Mat dst;
    	cout << "原图像的类型: " << img.type() << endl; // 16: CV_8UC3
    	img.convertTo(img, CV_32F);
    	cout << "现图像的类型: " << dst.type() << endl; // 21: CV_32FC3
    	normalize(img, dst, 1.0, 0, NORM_MINMAX);
    	cout << "归一化的类型: " << dst.type() << endl; // 21: CV_32FC3
    	imshow("图像数据归一化", dst);
    }
    
  • 测试 图像像素类型转换与归一化

  • 16 图像像素类型转换与归一化.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像像素类型转换与归一化
    	qd.normalizeOperation(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十六、图像缩放与插值

1、知识点

(1) resize

作用:调整图像大小

语法:cv::resize (InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR )

参数解释:

  • src:输入图像。
  • ds:输出图像;它的大小为 dsize(当它非零时)或从 src.size()、fx 和 fy 计算的大小;dst 的类型与 src 的类型相同。
  • dsize:输出图像大小;如果它等于零,则计算为:dsize = Size(round(fxsrc.cols), round(fysrc.rows))。dsize 或 fx 和 fy 必须为非零。
  • fx:沿水平轴的比例因子;当它等于 0 时,它被计算为(double)dsize.width/src.cols
  • fy:沿垂直轴的比例因子;当它等于 0 时,它被计算为(double)dsize.height/src.rows
  • interpolation:插值方法,请参阅 InterpolationFlags (OpenCV:几何图像转换)
    • INTER_NEAREST:最近邻插值
    • INTER_LINEAR:双线性插值
    • INTER_CUBIC:双三次插值
    • INTER_LANCZOS4:8x8邻域上的兰索斯插值
    • INTER_AREA:使用像素面积关系进行重采样。它可能是图像抽取的首选方法,因为它可以提供无摩尔纹的结果。但是当图像缩放时,它类似于INTER_NEAREST方法。
    • INTER_LINEAR_EXACT:位精确双线性插值
    • INTER_NEAREST_EXACT:位精确最近邻插值。这将产生与PIL,scikit-image或Matlab中的最近邻方法相同的结果。
    • INTER_MAX:插值码的掩码
    • WARP_FILL_OUTLIERS:标志,填充所有目标图像像素。如果其中一些对应于源图像中的异常值,则将其设置为零
    • WARP_INVERSE_MAP:标志,逆变换例如,线性极坐标或对数极坐标变换:

2、示例

  • 在头文件的 QuickDemo 中添加 void resizeOperation(Mat& img); 成员函数

    	// 16. 图像缩放与插值
    	void resizeOperation(Mat& img);
    
  • 在源文件中 实现 void resizeOperation(Mat& img);

    void QuickDemo::resizeOperation(Mat& img)
    {
    	imshow("原图", img);
    	Mat enlarge, shrink;
    	int h = img.rows;
    	int w = img.cols;
    	// 缩小
    	resize(img, shrink, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);
    	imshow("shrink", shrink);
    	// 放大
    	resize(img, enlarge, Size(w * 1.5, h * 1.5), 0, 0, INTER_LINEAR);
    	imshow("enlarge", enlarge);
    }
    
  • 测试 图像缩放与插值函数

  • 17 图像缩放与插值.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像缩放与插值
    	qd.resizeOperation(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

3、练习

#include <iostream>
#include "quickopencv.h"
using namespace std;

double scale = 1;

void on_change(int event, int x, int y, int flags, void* userdata)
{
	Mat dst;
	Mat image = *((Mat*)userdata);
	int h = image.rows;
	int w = image.cols;
	bool change = false;
	// 判断鼠标事件
	if (event == EVENT_MOUSEWHEEL)
	{
		if (flags > 0)
		{
			if (scale <= 2)
			{
				scale += 0.1;
				change = true;
			}
		}
		else
		{
			if (scale >= 0.5)
			{
				scale -= 0.1;
				change = true;
			}
		}
		// 图像缩放
		if (change)
		{
			cout << flags << endl;
			resize(image, dst, Size(w * scale, h * scale), 0, 0, INTER_LINEAR);
			imshow("鼠标控制大小", dst);
		}
		
 	}
}

void imageResize(Mat& img)
{
	namedWindow("鼠标控制大小", WINDOW_AUTOSIZE);
	setMouseCallback("鼠标控制大小", on_change, (void*)(&img));
	imshow("鼠标控制大小", img);
}

int main(int argc, char** argv)
{
	// 1. 实例化
	QuickDemo qd;
	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
	if (src.empty())
	{
		cout << "图片读取失败!!!" << endl;
		return -1;
	}
	imageResize(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

十七、图像翻转

1、知识点

(1) flip

作用:图像翻转

语法:void flip(InputArray src, OutputArray dst, int flipCode);

参数解释:

  • src:输入图像
  • dst:输出图像
  • flipCode:一个标志来指定如何翻转数组;0 表示围绕 x 轴翻转,正值(例如 1)表示绕 y 轴翻转。负值(例如 -1)表示围绕两个轴翻转。

2、示例

  • 在头文件的 QuickDemo 中添加 void imageFlip(Mat& img); 成员函数

    	// 17. 图像翻转
    	void imageFlip(Mat& img);
    
  • 在源文件中 实现 void imageFlip(Mat& img);

    void QuickDemo::imageFlip(Mat& img)
    {
    	int h = img.rows;
    	int w = img.cols;
    	Mat image;
    	resize(img, image, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);
    	imshow("原图", image);
    	Mat dst;
    	// 上下翻转
    	flip(image, dst, 0);
    	imshow("图像翻转 - Y轴", dst);
    	// 左右翻转
    	flip(image, dst, 1);
    	imshow("图像翻转 - X轴", dst);
    	// 上下左右翻转
    	flip(image, dst, -1);
    	imshow("图像翻转 - XY轴", dst);
    }
    
  • 测试 图像翻转函数

  • 18 图像翻转.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像翻转
    	qd.imageFlip(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十八、图像旋转

1、知识点

(1) warpAffine

作用:仿射变换

语法:

void warpAffine( InputArray src, OutputArray dst,
                InputArray M, Size dsize,
                int flags = INTER_LINEAR,
                int borderMode = BORDER_CONSTANT,
                const Scalar& borderValue = Scalar());

参数解释:

  • src:输入图像
  • dst:borderValue
  • M:2\乘以 3 变换矩阵。2×3
  • dsize:输出图像的大小。
  • flags:插值方法
    • INTER_NEAREST:最近邻插值
    • INTER_LINEAR:双线性插值
    • INTER_CUBIC:双三次插值
    • INTER_LANCZOS4:8x8邻域上的兰索斯插值
    • INTER_AREA:使用像素面积关系进行重采样。它可能是图像抽取的首选方法,因为它可以提供无摩尔纹的结果。但是当图像缩放时,它类似于INTER_NEAREST方法。
    • INTER_LINEAR_EXACT:位精确双线性插值
    • INTER_NEAREST_EXACT:位精确最近邻插值。这将产生与PIL,scikit-image或Matlab中的最近邻方法相同的结果。
    • INTER_MAX:插值码的掩码
    • WARP_FILL_OUTLIERS:标志,填充所有目标图像像素。如果其中一些对应于源图像中的异常值,则将其设置为零
    • WARP_INVERSE_MAP:标志,逆变换例如,线性极坐标或对数极坐标变换:
  • borderMode:像素外推法
    • BORDER_CONSTANT
    • BORDER_REPLICATE
    • BORDER_REFLECT
    • BORDER_WRAP
    • BORDER_REFLECT_101
    • BORDER_TRANSPARENT
    • BORDER_REFLECT101
    • BORDER_DEFAULT
    • BORDER_ISOLATED
  • borderValue:在恒定边框的情况下使用的值;默认情况下,它为 0。

(2) getRotationMatrix2D

作用:计算二维旋转的仿射矩阵

函数原型:Mat getRotationMatrix2D(Point2f center, double angle, double scale)

函数参数:

  • center:图像的旋转中心
  • angle:旋转角度(规定坐标原点左上角,正值表示逆时针旋转)
  • scale:各向同性比例因子(对图像本身大小的放缩)

(3) 扩展

通过如上图,我们可以计算新图像的宽度,高度,旋转中心的通式

  • 新宽度nw = wcosΘ + hsinΘ
  • 新宽度nh = wsinΘ + hcosΘ
  • 新旋转中心x +=( nw/2 - w/2)
  • 新旋转中心y +=( nh/2 - h/2)

2、示例

  • 在头文件的 QuickDemo 中添加 void imageRotate(Mat& img); 成员函数

    	// 18. 图像旋转
    	void imageRotate(Mat& img);
    
  • 在源文件中 实现 void imageRotate(Mat& img);

    void QuickDemo::imageRotate(Mat& img)
    {
    	Mat image;
    	resize(img, image, Size(img.cols / 2, img.rows / 2), 0, 0, INTER_LINEAR);
    	imshow("原图", image);
    
    	Mat dst, M;
    	int h = image.rows;
    	int w = image.cols;
    	// ------------------------------ 旋转 45 度 ------------------------------
    	// 计算二维旋转的仿射矩阵
    	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 n_w = cos * w + sin * h;
    	int n_h = sin * w + cos * h;
    	M.at<double>(0, 2) += (n_w / 2 - w / 2);
    	M.at<double>(1, 2) += (n_h / 2 - h / 2);
    	warpAffine(image, dst, M, Size(n_w, n_h));
    	imshow("45°", dst);
    
    	// ------------------------------ 旋转 135 度 ------------------------------
    	// 计算二维旋转的仿射矩阵
    	M = getRotationMatrix2D(Point2f(w / 2, h / 2), 135, 1.0);
    	// 计算新图像的宽度,高度,旋转中心
    	cos = abs(M.at<double>(0, 0));
    	sin = abs(M.at<double>(0, 1));
    	n_w = cos * w + sin * h;
    	n_h = sin * w + cos * h;
    	M.at<double>(0, 2) += (n_w / 2 - w / 2);
    	M.at<double>(1, 2) += (n_h / 2 - h / 2);
    	warpAffine(image, dst, M, Size(n_w, n_h), INTER_LINEAR, 0, Scalar(255, 0, 0));
    	imshow("135°", dst);
    }
    
  • 测试 图像翻转函数

  • 19 图像旋转.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	Mat src = imread("F:/datasets/Test Sample/images/image_503.jpg");
    	if (src.empty())
    	{
    		cout << "图片读取失败!!!" << endl;
    		return -1;
    	}
    	// 2. 创建一个窗口
    	// namedWindow("输入窗口", WINDOW_FREERATIO);
    
    	// 3. 显示
    	// imshow("输入窗口", src);
    
    	// 4. 图像旋转
    	qd.imageRotate(src);
    
    	// 5. 阻塞
    	waitKey(0);
    
    	// 6. 关闭所有窗口
    	destroyAllWindows();
    
    	// 7. 等待用户输入
    	// system("pause");
    	return 0;
    }
    
  • 测试结果

十九、视频文件&摄像头使用

1、知识点

(1) VideoCapture

作用:打开视频

语法:VideoCapture cap(url);

参数解释:

  • url:视频链接

(2) cap.read

作用:读取帧

语法:cap.read(frame);

参数解释:

  • frame:输出图像

(3) cap.release

作用:释放摄像头

语法:cap.release()

2、示例

  • 在头文件的 QuickDemo 中添加 void readVideo(Mat& img); 成员函数

    	// 19. 视频文件&摄像头使用
    	void readVideo(const string& url);
    
  • 在源文件中 实现 void readVideo(Mat& img);

    void QuickDemo::readVideo(const string& url)
    {
    	cout << url << endl;
    	// 1. 读取本机摄像头 VideoCapture cap(0);
    	
    	// 2. 读取本地文件&摄像头地址
    	VideoCapture cap(url);
        
        // 3. 获取视频信息
    	int frame_width = cap.get(CAP_PROP_FRAME_WIDTH);	// 宽
    	int frame_height = cap.get(CAP_PROP_FRAME_HEIGHT);	// 高
    	int count = cap.get(CAP_PROP_FRAME_COUNT);			// 帧数
    	double fps = cap.get(CAP_PROP_FPS);					// 帧率
    	cout << "视频宽度: " << frame_width << endl;
    	cout << "视频高度: " << frame_height << endl;
    	cout << "视频帧数: " << count << endl;
    	cout << "视频帧率: " << fps << endl;
    
    	Mat frame;
    	while (true)
    	{
    		// 抽帧
    		/*
    			读取方式一  
    			cap.read(frame);
    			读取方式二 
    			cap >> frame;  
    		*/
    		cap.read(frame);
    		if (frame.empty())
    		{
    			break;
    		}
    		imshow("frame", frame);
    		if (waitKey(10) == 27)
    		{
    			break;
    		}
    	}
        cap.release();
    	destroyAllWindows();
    }
    
  • 测试 视频文件&摄像头使用函数

  • 20 视频文件&摄像头使用.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	// string url = "F:/datasets/Test Sample/videos/cam1.mp4";
    	string url = "rtsp://admin:syyt@123@192.168.1.25";
    
    	// 2. 视频读取
    	qd.readVideo(url);
    
    	return 0;
    }
    
  • 测试结果

二十、视频处理与保存

1、知识点

2、示例

  • 在头文件的 QuickDemo 中添加 void saveVideo(Mat& img); 成员函数

    	// 20. 视频处理与保存
    	void saveVideo(const string& url);
    
  • 在源文件中 实现 void saveVideo(Mat& img);

    void QuickDemo::saveVideo(const string& url)
    {
    	cout << url << endl;
    	// 1. 读取本机摄像头 VideoCapture cap(0);
    
    	// 2. 读取本地文件&摄像头地址
    	VideoCapture cap(url);
    
    	// 3. 获取视频信息
    	int frame_width = cap.get(CAP_PROP_FRAME_WIDTH);	// 宽
    	int frame_height = cap.get(CAP_PROP_FRAME_HEIGHT);	// 高
    	int count = cap.get(CAP_PROP_FRAME_COUNT);			// 帧数
    	double fps = cap.get(CAP_PROP_FPS);					// 帧率
    	int fourcc = cap.get(CAP_PROP_FOURCC);				// 编码格式
    	cout << "视频宽度: " << frame_width << endl;
    	cout << "视频高度: " << frame_height << endl;
    	cout << "视频帧数: " << count << endl;
    	cout << "视频帧率: " << fps << endl;
    	cout << "视频编码: " << fourcc << endl;
    
    	// 4. 保存文件
    	VideoWriter video_writer("F:/datasets/Test Sample/videos/save_video_demo.mp4", fourcc, fps, Size(frame_width, frame_height), true);
    
    	Mat frame, gray;
    	while (true)
    	{
    		// 抽帧
    		cap.read(frame);
    		if (frame.empty())
    		{
    			break;
    		}
    		// 1. 帧图转灰度图
    		cvtColor(frame, gray, COLOR_BGR2GRAY);
    		// 显示原图
            imshow("frame", frame);
            // 保存灰度图
    		video_writer.write(gray);
    		if (waitKey(10) == 27)
    		{
    			break;
    		}
    	}
    	cap.release();
    	video_writer.release();
    	destroyAllWindows();
    }
    
  • 测试 视频处理与保存函数

  • 21 视频处理与保存.cpp 中调用

    #include <iostream>
    #include "quickopencv.h"
    using namespace std;
    
    int main(int argc, char** argv)
    {
    	// 1. 实例化
    	QuickDemo qd;
    	// string url = "F:/datasets/Test Sample/videos/cam1.mp4";
    	string url = "rtsp://admin:syyt@123@192.168.1.25";
    
    	// 2. 视频保存
    	qd.saveVideo(url);
    
    	return 0;
    }
    
  • 测试结果

二十一、图像直方图

1、知识点

图像直方图的解释

图像直方图是图像像素值的统计学特征、计算代价较小,具有图像平移、旋转、缩放不变性等众多优点,广泛地应用于图像处理的各个领减,特别是灰度图像的阈值分割、基于颜色的图像检索以及图像分类、反向投影跟踪。常见的分为

  • 灰度直方图
  • 颜色直方图

Bis是指直方图的大小范围,对于像察值取值在0~255之间的,最少有256个

bin,此外还可以有16、32、48.128等,256除以bin的大小应该是整数倍.

OpenCVi中相关API

  • calcHist(&bgr_plane[0],1,0,Mat(),b_hist,1,bins,ranges);

(1) calcHist

作用:计算Blue, Green, Red通道直方图

语法:

calcHist( const Mat* images, int nimages,
         const int* channels, InputArray mask,
         OutputArray hist, int dims, const int* histSize,
         const float** ranges, bool uniform = true, bool accumulate = false );

参数解释:

  • images:图像数组

  • nimages:输入图像数量

  • channels:通道数

  • mask:可选mask

  • hist:输出直方图数据(值与对应频次)的n维数组

  • dims:直方图维数

    • 当通道为1个时,我们选择维度为1维,此时直方图数据就为一维数组
    • 当维度为2个时,我们选择维度为2维,此时直方图数据就为二维数组
    • ………………
    • 最大支持32维
    • 也就是说,n张图像 每张图像m个通道 可以计算出相应的直方图数据
    • 对于绘制来说,一般都只绘制到2维,3维及以上就很复杂了
  • histSize:bins数组,x轴长度

  • ranges:取值范围数组

  • uniform:指示直方图bin间隔是否一致

    • 默认为true,即等间隔取值
    • 如果为false,则range不能写{0,255}这种,就要写{1,1,……,1}这种
  • accumulate:累计标志(默认为false)

    • 当多张图像的时候,如果为true,则绘制直每张方图的时候,不会从头清空,会在前者直方图的基础上继续

(2) cvRound

作用:将浮点数四舍五入到最近的整数

语法:cvRound( double value )

参数解释:

  • value:要处理的浮点数

(3)

2、示例

  • 在头文件的 QuickDemo 中添加 void saveVideo(Mat& img); 成员函数

    	// 21. 图像直方图
    	void imageHistogram(Mat& img);
    
  • 在源文件中 实现 void saveVideo(Mat& img);

    void QuickDemo::imageHistogram(Mat& img)
    {
    	// 显示原图
    	imshow("src", img);
    
    	// 图像直方图
    	// 1. 三通道分离
    	vector<Mat> bgr_plane;
    	split(img, 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]);
    	Mat histImage = Mat::zeros(hist_w, hist_h, CV_8UC3);
    	// 归一化直方图数据
    	normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    	normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    	normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    	// 绘制直方图曲线
    	for (int i = 0; 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(0, 255, 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(0, 0, 255),
    			2,
    			8,
    			0
    		);
    	}
    	// 显示直方图
    	namedWindow("直方图", WINDOW_AUTOSIZE);
    	imshow("直方图", histImage);
    
    	// 等待关闭
    	waitKey(0);
    
    	// 关闭窗口
    	destroyAllWindows();
    }
    
  • 测试 图像直方图函数

  • 22 图像直方图.cpp 中调用

    #include <iostream>
    #include <opencv2\opencv.hpp>
    #include "quickopencv.h"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    	// 实例化
    	QuickDemo qd;
    
    	// 读取图片
    	Mat src = imread("F:/resources/images/Atlas/1/3a64b398fe2eb60a4c6121e7148e6edf.jpg", 1);
    	
    	// 图像直方图
    	qd.imageHistogram(src);
    
    	// 等待用户输入
    	system("pause");
    
    	// 函数返回
    	return 0;
    }
    
  • 测试结果

二十二、二维直方图

1、知识点

(1) calcHist

作用:二维直方图

语法:

calcHist( const Mat* images, int nimages,
         const int* channels, InputArray mask,
         OutputArray hist, int dims, const int* histSize,
         const float** ranges, bool uniform = true, bool accumulate = false );

参数解释:

  • images:图像数组
  • nimages:输入图像数量
  • channels:通道数组
  • mask:可选mask
  • hist:输出直方图数据(值与对应频次)的n维数组
  • dims:直方图维数
    • 当通道为1个时,我们选择维度为1维,此时直方图数据就为一维数组
    • 当维度为2个时,我们选择维度为2维,此时直方图数据就为二维数组
    • ………………
    • 最大支持32维
    • 也就是说,n张图像 每张图像m个通道 可以计算出相应的直方图数据
    • 对于绘制来说,一般都只绘制到2维,3维及以上就很复杂了
  • histSize:bins数组,x轴长度
  • ranges:取值范围数组
  • uniform:指示直方图bin间隔是否一致
    • 默认为true,即等间隔取值
    • 如果为false,则range不能写{0,255}这种,就要写{1,1,……,1}这种
  • accumulate:累计标志(默认为false)
    • 当多张图像的时候,如果为true,则绘制直每张方图的时候,不会从头清空,会在前者直方图的基础上继续

2、示例

  • 在头文件的 QuickDemo 中添加 void imageHistogram2d(Mat& img); 成员函数

    	// 22. 二维直方图
    	void imageHistogram2d(Mat& img);
    
  • 在源文件中 实现 void imageHistogram2d(Mat& img);

    void QuickDemo::imageHistogram2d(Mat& img)
    {
    	// 显示原图
    	imshow("src", img);
    
    	// 2D 直方图
    	Mat hsv, hs_hist;
    	cvtColor(img, hsv, COLOR_BGR2HSV);
    
    	int hbins = 30;
    	int 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);
    
    	std::cout << hs_hist;
    
    	double maxVal = 0;//寻找直方图数据中的最大值
    	minMaxLoc(hs_hist, 0, &maxVal, 0, 0);
    
    
    	int scale = 10;
    	//行320 列300
    	Mat hist2d_image = Mat::zeros(sbins * scale, hbins * scale, CV_8UC3);
    
    	//h30行,s32列,一行一行的绘制矩形
    	for (int h = 0; h < hbins; h++) {
    		for (int s = 0; s < sbins; s++)
    		{
    			float binVal = hs_hist.at<float>(h, s);//位于横h,列s处的频次
    
    			int intensity = cvRound(binVal * 255 / maxVal);//白色的强度,频次越大,小矩形越接近白色
    
    			Point p1(h * scale, s * scale);
    			/*
    				矩形左上角的点
    			*/
    			Point p2((h + 1) * scale - 1, (s + 1) * scale - 1);
    			/*
    				矩形右下角的点
    				-1只是为了不与其他矩形的左上角重合,不-1差异也不大
    			*/
    
    			rectangle(hist2d_image, p1, p2, Scalar::all(intensity), -1);
    		}
    	}
    
    	//灰色的图像不容看出差异,这里我们转换色彩风格
    	applyColorMap(hist2d_image, hist2d_image, COLORMAP_DEEPGREEN);
    
    	// 显示直方图
    	namedWindow("直方图", WINDOW_AUTOSIZE);
    	imshow("直方图", hist2d_image);
    
    	// 等待关闭
    	waitKey(0);
    
    	// 关闭窗口
    	destroyAllWindows();
    }
    
  • 测试 二维直方图函数

  • 23 二维直方图.cpp 中调用

    #include <iostream>
    #include <opencv2\opencv.hpp>
    #include "quickopencv.h"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    	// 实例化
    	QuickDemo qd;
    
    	// 读取图片
    	Mat src = imread("F:/datasets/TestSample/images/demo_01.jpg", 1);
    
    	// 二维直方图
    	qd.imageHistogram2d(src);
    
    	// 等待用户输入
    	system("pause");
    
    	// 函数返回
    	return 0;
    }
    
  • 测试结果

二十三、直方图均衡化

1、知识点

图像直方图均衡化可以用于图像增强、对输入图像进行直方图均衡化处理,提升后续对象检测的准确率在0peCV人脸检测的代码演示中已经很常见。此外对医学影像图像与卫星遥感图像也经常通过直方图均衡化来提升图像质量。

OpenCV中直方图均衡化的API很简单

  • qualizeHist(src,dst)

(1) qualizeHist

作用:图像直方图均衡化

语法:qualizeHist(src,dst)

参数解释:

  • src:输入图像
  • dst:输出图像

2、示例

  • 在头文件的 QuickDemo 中添加 void histogramEqualization(Mat& img); 成员函数

    	// 23. 直方图均衡化
    	void histogramEqualization(Mat& img);
    
  • 在源文件中 实现 void histogramEqualization(Mat& img);

    void QuickDemo::histogramEqualization(Mat& img)
    {
    	imshow("原图", img);
    	Mat gray;
    	// 转为灰度图
    	cvtColor(img, gray, COLOR_BGR2GRAY);
    	imshow("灰度图", gray);
    	// 直方图均衡化
    	Mat dst;
    	equalizeHist(gray, dst);
    	imshow("直方图均衡化", dst);
    
    	// 等待关闭
    	waitKey(0);
    
    	// 关闭窗口
    	destroyAllWindows();
    }
    
  • 测试 直方图均衡化函数

  • 24 直方图均衡化.cpp 中调用

    #include <iostream>
    #include <opencv2\opencv.hpp>
    #include "quickopencv.h"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    	// 实例化
    	QuickDemo qd;
    
    	// 读取图片
    	Mat src = imread("F:/datasets/TestSample/images/demo_01.jpg", 1);
    
    	// 直方图均衡化
    	qd.histogramEqualization(src);
    
    	// 等待用户输入
    	system("pause");
    
    	// 函数返回
    	return 0;
    }
    
  • 测试结果

二十四、图像卷积操作

图像卷积操作

图像卷积可以看成是一个窗口区域在另外一个大的图像上移动,对每个窗口覆盖的区域都进行点乘得到的值作为中心像素点的输出值。窗口的移动是从左到右,从上到下。窗口可以理解成一个指定大小的二维矩阵,里面有预先指定的值。

(1) blur

作用:图像卷积

语法:void blur( InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT );

参数解释:

  • src:输入图像
  • dst:输出图像
  • ksize:卷积核大小
  • anchor:中心点位置
  • borderType:边框类型

2、示例

  • 在头文件的 QuickDemo 中添加 void imageBlur(Mat& img); 成员函数

    	// 24. 图像卷积操作
    	void imageBlur(Mat& img);
    
  • 在源文件中 实现 void imageBlur(Mat& img);

    void QuickDemo::imageBlur(Mat& img)
    {
    	imshow("原图", img);
    	
    	// 图像卷积
    	Mat dst;
    	blur(img, dst, Size(3, 3), Point(-1, -1));
    	imshow("图像卷积", dst);
    	
    	// 等待关闭
    	waitKey(0);
    
    	// 关闭窗口
    	destroyAllWindows();
    }
    
  • 测试 图像卷积操作函数

  • 24 图像卷积操作.cpp 中调用

    #include <iostream>
    #include <opencv2\opencv.hpp>
    #include "quickopencv.h"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    	// 实例化
    	QuickDemo qd;
    
    	// 读取图片
    	Mat src = imread("F:/datasets/TestSample/images/demo_01.jpg", 1);
    
    	// 图像卷积操作
    	qd.imageBlur(src);
    
    	// 等待用户输入
    	system("pause");
    
    	// 函数返回
    	return 0;
    }
    
  • 测试结果

二十五、高斯模糊

1、知识点

(1) GaussianBlur

作用:高斯模糊

语法:void GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT );

参数解释:

  • src:输入图像
  • dst:输出图像
  • ksize:卷积核大小
  • sigmaX:σ
  • sigmaY:σ
  • borderType :

2、示例

  • 在头文件的 QuickDemo 中添加 void imageGaussianBlur(Mat& img); 成员函数

    	// 25. 高斯模糊
    	void imageGaussianBlur(Mat& img);
    
  • 在源文件中 实现 void imageGaussianBlur(Mat& img);

    void QuickDemo::imageGaussianBlur(Mat& img)
    {
    	imshow("原图", img);
    
    	// 高斯模糊
    	Mat dst;
    	GaussianBlur(img, dst, Size(0, 0), 15);
    	imshow("高斯模糊", dst);
    
    	// 等待关闭
    	waitKey(0);
    
    	// 关闭窗口
    	destroyAllWindows();
    }
    
  • 测试 高斯模糊函数

  • 25 高斯模糊.cpp 中调用

    #include <iostream>
    #include <opencv2\opencv.hpp>
    #include "quickopencv.h"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    	// 实例化
    	QuickDemo qd;
    
    	// 读取图片
    	Mat src = imread("F:/datasets/TestSample/images/demo_01.jpg", 1);
    
    	// 高斯模糊
    	qd.imageGaussianBlur(src);
    
    	// 等待用户输入
    	system("pause");
    
    	// 函数返回
    	return 0;
    }
    
  • 测试结果

二十六、高斯双边模糊

1、知识点

(1) bilateralFilter

作用:双边模糊

语法:void bilateralFilter( InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT );

参数解释:

  • src:输入图像
  • dst:输出图像
  • d:
  • sigmaColor:
  • sigmaSpace:
  • borderType:

2、示例

  • 在头文件的 QuickDemo 中添加 void imageGaussianBifilter(Mat& img); 成员函数

    	// 26. 高斯双边模糊
    	void imageGaussianBifilter(Mat& img);
    
  • 在源文件中 实现 void imageGaussianBifilter(Mat& img);

    void QuickDemo::imageGaussianBifilter(Mat& img)
    {
    	imshow("原图", img);
    
    	// 高斯双边模糊
    	Mat dst;
    	bilateralFilter(img, dst, 0, 100, 10);
    	imshow("高斯双边模糊", dst);
    
    	// 等待关闭
    	waitKey(0);
    
    	// 关闭窗口
    	destroyAllWindows();
    }
    
  • 测试 高斯双边模糊函数

  • 26 高斯双边模糊.cpp 中调用

    #include <iostream>
    #include <opencv2\opencv.hpp>
    #include "quickopencv.h"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    	// 实例化
    	QuickDemo qd;
    
    	// 读取图片
    	Mat src = imread("F:/datasets/TestSample/images/demo_01.jpg", 1);
    
    	// 高斯模糊
    	qd.imageGaussianBifilter(src);
    
    	// 等待用户输入
    	system("pause");
    
    	// 函数返回
    	return 0;
    }
    
  • 测试结果

二十七、实战案例

1、实时人脸检测

(1) 方案一

#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
#include <cstdio>
using namespace std;
using namespace cv;

//定义7种颜色,用于标记人脸
Scalar colors[] =
{
	//红橙黄绿青蓝紫【RGB】
	CV_RGB(255, 0, 0),
	CV_RGB(255, 97, 0),
	CV_RGB(255, 255, 0),
	CV_RGB(0, 255, 0),
	CV_RGB(0, 255, 255),
	CV_RGB(0, 0, 255),
	CV_RGB(160, 32, 240)
};

int main()
{
	//读取训练器 haar
	CascadeClassifier classifier_face;
	string haarfileFace = "D:\\software\\Opencv\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt.xml";//【[opencv安装地址]\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt.xml】
	CascadeClassifier classifier_eye;
	string haarfileEye = "D:\\software\\Opencv\\opencv\\build\\etc\\haarcascades\\haarcascade_eye.xml";

	if (!classifier_face.load(haarfileFace))//加载文件
	{
		cout << "coulud not load face image..." << endl;
		return -1;
	}
	if (!classifier_eye.load(haarfileEye))
	{
		cout << "coulud not load eye image..." << endl;
		return -1;
	}

	Mat srcImage, grayImage, grayImage1, dstImage;
	srcImage = imread("F:/datasets/TestSample/images/image_577.jpeg");//读取图片
	/*imshow("原图", srcImage);
	waitKey(0);*/
	dstImage = srcImage.clone();//复制一张图片
	cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);//生成灰度图 提高检测效率

	//检测人脸
	vector<Rect> faceRect;
	classifier_face.detectMultiScale(grayImage, faceRect, 1.1, 3, 0);//调用分类器对象
	cout << "检测到人脸个数:" << faceRect.size() << endl;

	if (faceRect.size())
	{
		for (int i = 0; i < faceRect.size(); i++)
		{
			cv::rectangle(dstImage, faceRect[i], Scalar(0, 0, 255), 2);//标记--在脸部画矩形
			//Point center;
			//int radius;
			//center.x = cvRound((faceRect[i].x + faceRect[i].width * 0.5));
			//center.y = cvRound((faceRect[i].y + faceRect[i].height * 0.5));
			//radius = cvRound((faceRect[i].width + faceRect[i].height) * 0.25);
			//circle(dstImage, center, radius, colors[i % 7], 3);//标记--在脸部画圆(画矩形和画圆二选一)

			//在人脸图像中检测人眼和嘴巴
			Mat faceimg = dstImage(faceRect[i]);//获取人脸矩形图像
			/*imshow("faceimg", faceimg);
			waitKey(0);*/
			cvtColor(faceimg, grayImage1, COLOR_BGR2GRAY);
			//检测眼睛和嘴巴
			vector<Rect> eyeRect;
			classifier_eye.detectMultiScale(grayImage1, eyeRect, 1.1, 3, 0);
			if (eyeRect.size())
			{
				for (int j = 0; j < eyeRect.size(); j++)
				{
					// 标记--在眼部画矩形
					//Rect tmpRect;
					//tmpRect.x = faceRect[i].x + eyeRect[j].x;
					//tmpRect.y = faceRect[i].y + eyeRect[j].y;
					//tmpRect.width = eyeRect[j].width;
					//tmpRect.height = eyeRect[j].height;
					//cv::rectangle(dstImage, tmpRect, Scalar(255, 0, 0));

					//标记--在眼部画圆(画矩形和画圆二选一)
					Point centerEye;
					int radiusEye;
					centerEye.x = cvRound((faceRect[i].x + eyeRect[j].x + eyeRect[j].width * 0.5));
					centerEye.y = cvRound((faceRect[i].y + eyeRect[j].y + eyeRect[j].height * 0.5));
					radiusEye = cvRound((eyeRect[j].width + eyeRect[j].height) * 0.25);
					circle(dstImage, centerEye, radiusEye, colors[i % 7], 2);
				}
			}
		}
	}
	// imwrite("rgDetect.png", dstImage);
	//显示最终检测结果
	imshow("【人脸识别detectMultiScale】", dstImage);
	waitKey(0);

	system("pause");
	return 0;
}

(2) 方案二

#include "opencv2/opencv.hpp"
#include <map>
#include <vector>
#include <string>
#include <iostream>
using namespace cv;
using namespace std;

const std::map<std::string, int> str2backend{
    {"opencv", cv::dnn::DNN_BACKEND_OPENCV}, {"cuda", cv::dnn::DNN_BACKEND_CUDA},
    // {"timvx",  cv::dnn::DNN_BACKEND_TIMVX},  {"cann", cv::dnn::DNN_BACKEND_CANN}
};
const std::map<std::string, int> str2target{
    {"cpu", cv::dnn::DNN_TARGET_CPU}, {"cuda", cv::dnn::DNN_TARGET_CUDA},
    {"npu", cv::dnn::DNN_TARGET_NPU}, {"cuda_fp16", cv::dnn::DNN_TARGET_CUDA_FP16}
};

class YuNet
{
public:
    YuNet(const std::string& model_path,
        const cv::Size& input_size = cv::Size(320, 320),
        float conf_threshold = 0.6f,
        float nms_threshold = 0.3f,
        int top_k = 5000,
        int backend_id = 0,
        int target_id = 0)
        : model_path_(model_path), input_size_(input_size),
        conf_threshold_(conf_threshold), nms_threshold_(nms_threshold),
        top_k_(top_k), backend_id_(backend_id), target_id_(target_id)
    {
        model = cv::FaceDetectorYN::create(model_path_, "", input_size_, conf_threshold_, nms_threshold_, top_k_, backend_id_, target_id_);
    }

    void setBackendAndTarget(int backend_id, int target_id)
    {
        backend_id_ = backend_id;
        target_id_ = target_id;
        model = cv::FaceDetectorYN::create(model_path_, "", input_size_, conf_threshold_, nms_threshold_, top_k_, backend_id_, target_id_);
    }

    /* Overwrite the input size when creating the model. Size format: [Width, Height].
    */
    void setInputSize(const cv::Size& input_size)
    {
        input_size_ = input_size;
        model->setInputSize(input_size_);
    }

    cv::Mat infer(const cv::Mat image)
    {
        cv::Mat res;
        model->detect(image, res);
        return res;
    }

private:
    cv::Ptr<cv::FaceDetectorYN> model;

    std::string model_path_;
    cv::Size input_size_;
    float conf_threshold_;
    float nms_threshold_;
    int top_k_;
    int backend_id_;
    int target_id_;
};

cv::Mat visualize(const cv::Mat& image, const cv::Mat& faces, float fps = -1.f)
{
    static cv::Scalar box_color{ 0, 255, 0 };
    static std::vector<cv::Scalar> landmark_color{
        cv::Scalar(255,   0,   0), // right eye
        cv::Scalar(0,   0, 255), // left eye
        cv::Scalar(0, 255,   0), // nose tip
        cv::Scalar(255,   0, 255), // right mouth corner
        cv::Scalar(0, 255, 255)  // left mouth corner
    };
    static cv::Scalar text_color{ 0, 255, 0 };

    auto output_image = image.clone();

    if (fps >= 0)
    {
        cv::putText(output_image, cv::format("FPS: %.2f", fps), cv::Point(0, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, text_color, 2);
    }

    for (int i = 0; i < faces.rows; ++i)
    {
        // Draw bounding boxes
        int x1 = static_cast<int>(faces.at<float>(i, 0));
        int y1 = static_cast<int>(faces.at<float>(i, 1));
        int w = static_cast<int>(faces.at<float>(i, 2));
        int h = static_cast<int>(faces.at<float>(i, 3));
        cv::rectangle(output_image, cv::Rect(x1, y1, w, h), box_color, 2);

        // Confidence as text
        float conf = faces.at<float>(i, 14);
        cv::putText(output_image, cv::format("%.4f", conf), cv::Point(x1, y1 + 12), cv::FONT_HERSHEY_DUPLEX, 0.5, text_color);

        // Draw landmarks
        for (int j = 0; j < landmark_color.size(); ++j)
        {
            int x = static_cast<int>(faces.at<float>(i, 2 * j + 4)), y = static_cast<int>(faces.at<float>(i, 2 * j + 5));
            cv::circle(output_image, cv::Point(x, y), 2, landmark_color[j], 2);
        }
    }
    return output_image;
}

int main(int argc, char** argv)
{
    // 模型地址
    std::string input_path = "F:/datasets/TestSample/images/new_116.jpg";
    std::string model_path = "F:/projects/C++/OpenCV_projects/demo01/weights/face_detection_yunet_2022mar.onnx";
    std::string backend = "cuda";
    std::string target = "cuda";
    float conf_threshold = 0.6f;
    float nms_threshold = 0.6f;
    int top_k = 0;
    const int backend_id = str2backend.at(backend);
    const int target_id = str2target.at(target);
    bool save_flag = true;
    bool vis_flag = false;
    // 实例化 YuNet
    YuNet model(model_path, cv::Size(320, 320), conf_threshold, nms_threshold, top_k, backend_id, target_id);

    // 2. 读取本地文件&摄像头地址
    VideoCapture cap("rtsp://admin:syyt@123@192.168.1.25");

    // 3. 获取视频信息
    int frame_width = cap.get(CAP_PROP_FRAME_WIDTH);	// 宽
    int frame_height = cap.get(CAP_PROP_FRAME_HEIGHT);	// 高
    int count = cap.get(CAP_PROP_FRAME_COUNT);			// 帧数
    double fps = cap.get(CAP_PROP_FPS);					// 帧率
    cout << "视频宽度: " << frame_width << endl;
    cout << "视频高度: " << frame_height << endl;
    cout << "视频帧数: " << count << endl;
    cout << "视频帧率: " << fps << endl;
    model.setInputSize(Size(frame_width, frame_height));
    auto tick_meter = cv::TickMeter();

    Mat frame;
    while (true)
    {
        // 抽帧
        /*
            读取方式一
            cap.read(frame);
            读取方式二
            cap >> frame;
        */
        tick_meter.start();
        cap.read(frame);
        if (frame.empty())
        {
            break;
        }
        auto faces = model.infer(frame);
        std::cout << cv::format("%d faces detected:\n", faces.rows);
        /*for (int i = 0; i < faces.rows; ++i)
        {
            int x1 = static_cast<int>(faces.at<float>(i, 0));
            int y1 = static_cast<int>(faces.at<float>(i, 1));
            int w = static_cast<int>(faces.at<float>(i, 2));
            int h = static_cast<int>(faces.at<float>(i, 3));
            float conf = faces.at<float>(i, 14);
            std::cout << cv::format("%d: x1=%d, y1=%d, w=%d, h=%d, conf=%.4f\n", i, x1, y1, w, h, conf);
        }*/
        tick_meter.stop();
        auto res_image = visualize(frame, faces, (float)tick_meter.getFPS());
        imshow("res_image", res_image);
        if (waitKey(10) == 27)
        {
            break;
        }
        tick_meter.reset();
    }
    cap.release();
    destroyAllWindows();

    return 0;
}

第二章 OpenCV 图像处理

一、处理图像文件和视频文件

1、 读取和写入图像文件

// 处理图像文件和视频文件 - 读取和写入图像文件
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

// 程序入口函数
int main() {
	Mat in_image, out_image;
	// 读取原始图像
	in_image = imread("./datas/images/input-001.jpg");
	if (in_image.empty()) {
		// 检查是否正常读取图像
		cout << "Error! Input image cannot be read..." << endl;
		return -1;
	}

	// 创建两个具有图像名称的窗口
	namedWindow("in_image", WINDOW_AUTOSIZE);
	namedWindow("out_image", WINDOW_AUTOSIZE);
	// 在之前创建的窗口中显示图像
	imshow("in_image", in_image);
	// 图像转灰度图
	cvtColor(in_image, out_image, COLOR_BGR2GRAY);
	imshow("out_image", out_image);
	// 等待用户关闭
	waitKey(0);
	// 写入图像
	imwrite("./datas/images/out_image.jpg", out_image);
	// 等待用户退出
	system("pause");
	return 0;
}

2、读取和写入视频文件

// 处理图像文件和视频文件 - 读取和写入视频文件
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

// 程序入口函数
int main() {
	Mat in_frame, out_frame;
	const char win1[] = "Grabbing...", win2[] = "Recording...";
	
	char file_out[] = "recorded.mp4";


	// 打开默认摄像头
	VideoCapture inVid(0);
	// 检查摄像头是否打开
	if (!inVid.isOpened()) {
		cout << "Error! Camera not ready..." << endl;
		return -1;
	}
	// 获取视频宽高
	int width = (int)inVid.get(CAP_PROP_FRAME_WIDTH);
	int height = (int)inVid.get(CAP_PROP_FRAME_HEIGHT);
	// 每秒帧数
	int fps = 30;

	cout << "视频宽度:" << width << " 视频高度:" << height << " 视频帧率:" << fps << endl;

	// 写入视频
	VideoWriter recVid(file_out, VideoWriter::fourcc('M', 'P', '4', 'V'), fps, Size(width, height), 0);
	if (!recVid.isOpened()) {
		cout << "Error! Video file not opened..." << endl;
		return -1;
	}
	// 为原始视频和最终视频创建两个窗口
	namedWindow(win1);
	namedWindow(win2);
	while (true) {
		// 从摄像头读取视频帧
		inVid >> in_frame;
		// 左右翻转
		flip(in_frame, in_frame, 1);
		// 将帧转换为灰度图
		cvtColor(in_frame, out_frame, COLOR_BGR2GRAY);
		// 将帧写入视频文件(编码并保存)
		recVid << out_frame;
		// recVid.write(out_frame);
		imshow(win1, in_frame);
		imshow(win2, out_frame);
		if (waitKey(1000 / fps) >= 0) {
			break;
		}
	}
	inVid.release();
	recVid.release();
	destroyAllWindows();
	// 等待用户退出
	system("pause");
	return 0;
}

相关函数:

  • double VideoCapture::get(int propId):这个函数为一个VideoCapture对象返回指定的属性值。在videoio.hpp头文件中包含了基于DC1394(IEEE 1394数码相机规范)属性的一个完整列表。
  • static int VideoWriter::fourcc(char c1,char c2,char c3,char c4):这个函数把四个字符连接起来形成一个fourcc码。在示例中,MSVC代表微软视频(仅在Windows上可用)。有效的fourcc码列表被发布在http://www.fourcc.org/codecs.php 上。
  • bool VideoWriter::isOpened():如果写入视频的对象被成功初始化,这个函数返回true。例如,使用一个不正确的编解码器会产生一个错误。
  • VideoCapture&VideoCapture::operator >> (Mat&image):这个函数抓取、解码并返回下一帧。这个方法和布尔函数 VideoCapture::read(OutputArray image)等价。可以使用这个函数而不使用函数 VideoCapture::grab(),然后使用 VideoCapture::retrieve().
  • VideoWriter&VideoWriter::operator << (const Mat&image):这个函数写入下一帧。这个方法和布尔函数 VideoWriter::write(const Mat&image)等价。
  • void VideoCapture::release():这个函数释放视频文件或采集设备。尽管在本示例中没有必要显式地包含,但为了说明它的使用,示例中仍包含了这个函数。

在本示例中,有一个读取/写入循环,可同时地获取并处理窗口事件。waitKey(1000 / fps) 函数调用负责执行这个任务。在这个示例中,1000/fps表示返回外部循环之前等待的毫秒数。尽管不精确,但对于录制的视频仍能获取每秒帧数的一个近似度量。

提示:注意,在一个系统中有效的fourcc码依赖于本地安装的编解码器。为了了解在本地系统中安装的fourcc编解码器是否可用,推荐http://mediaarea.net/en/MediaInfo 上对很多平台都可用的开源工具MediaInfo。

3、用户交互工具

第三章 OpenCV & YOLOv5

一、环境配置

1、Libtorch 安装

  1. 下载指定版本的 Libtorch

    下载地址:libtorch 1.12.0 - gpu release 版本

  2. 解压到你想放的路径(尽量避免中文)

  3. 配置环境变量

2、配置 OpenCV

(1) 用VS新建一个空项目

(2) 配置 OpenCV

  1. 右键项目选择属性

  2. 更改配置为所有配置并选择 VC++ 目录中的包含目录

    注意后边的配置都是在 所有配置 x64 中配置

  3. 添加头文件到包含目录 (刚才下载的OpenCV目录) opencv/build/include

  4. 添加库目录并保存

  5. 添加 OpenCV 库文件名字到 链接器-输入-附加依赖项,其中添加的名字和刚才配置的库目录中的lib文件名相同

  6. 应用 -> 确认

(3) 代码编写

  1. 在源文件添加一个文件测试OpenCV

  2. 添加如下代码进行测试,更改运行环境为 x64 点击运行

    #include <iostream>
    #include <opencv2/opencv.hpp>
    
    using namespace cv;
    using namespace std;
    
    int main() {
    	// 1. 读取图片
    	Mat img = cv::imread("F:/datasets/TestSample/images/demo_01.jpg");
    	// 2. 显示图片
    	imshow("img", img);
    	// 3. 等待关闭
    	waitKey(0);
    	// 4. 关闭所有窗口
    	destroyAllWindows();
    	// 5. 函数返回
    	return 0;
    }
    

(4) 测试结果

3、配置 LibTorch

(1) 环境配置

  1. 在属性 -> VC++目录 -> 包含目录中添加 LibTorch 头文件

  2. 在属性 -> VC++目录 -> 库目录中添加 LibTorch 的库目录

  3. 在属性 -> 链接器 -> 输入 -> 附加依赖项中添加LibTorch的依赖

  4. 添加测试代码测试

    #include <iostream>
    #include <opencv2/opencv.hpp>
    #include <torch/torch.h>
    
    using namespace cv;
    using namespace std;
    
    int main() {
    	// 1. 创建一个 3 × 3 的矩阵
    	torch::Tensor data = torch::rand({ 3, 3 });
    	// 2. 输出矩阵
    	cout << data << endl;
    	// 3. 判断是否支持 cuda
    	cout << torch::cuda::is_available() << endl;
    	if (torch::cuda::is_available())
    	{
    		cout << "GPU" << endl;
    	}
    	else
    	{
    		cout << "CPU" << endl;
    	}
    	// 4. 函数返回
    	return 0;
    }
    

    我机器上面是有 cuda 的但是这里显示没有 cuda,下面进行 cuda 配置可能有些版本不同

(2) 配置支持 cuda

  1. 在属性 -> 链接器 -> 命令行中添加如下字符串

    # 每个版本的命令不同 我的版本是 cuda:11.3; cudnn:11.3; libtorch: 1.12.0
    /INCLUDE:"?ignore_this_library_placeholder@@YAHXZ"
    
  2. 再次测试

二、YoloV5

步骤:

  1. 工具选择 CMake 或者 VS2019

  2. 下载 C++ 的 YoloV5 库:YoloV5-LibTorch

  3. YoloV5.h

    #pragma once
    // C/C++ 自带
    #include <time.h>  
    #include <memory>
    #include <iostream>
    
    // libTorch
    #include <torch/torch.h>
    #include <torch/script.h>
    
    // OpenCV
    #include <opencv2/opencv.hpp>
    #include <opencv2/core/core.hpp>  
    #include <opencv2/highgui/highgui_c.h>
    #include <opencv2/highgui/highgui.hpp>  
    #include <opencv2/imgproc/imgproc.hpp>
    
    // 命名空间
    using namespace cv;
    using namespace std;
    
    // 目标
    enum Det
    {
    	tl_x = 0,
    	tl_y = 1,
    	br_x = 2,
    	br_y = 3,
    	score = 4,
    	class_idx = 5
    };
    
    // 检测
    struct Detection
    {
    	Rect bbox;
    	float score;
    	int class_idx;
    };
    
    
    class YoloV5
    {
    public:
    	// 构造函数
    	YoloV5(string model_path, float conf_thres, float iou_thres);
    
    	// 析构函数
    	~YoloV5();
    
    	// 获取类型名称
    	vector<string> loadClassNames(const string& path);
    
    	// 
    	vector<float> letterBoxImage(const Mat& src, Mat& dst, const Size& out_size);
    
    	// 
    	void tensor2Detection(const at::TensorAccessor<float, 2>& offset_boxes, const at::TensorAccessor<float, 2>& det, vector<Rect>& offset_box_vec, vector<float>& score_vec);
    
    	// 
    	void scaleCoordinates(vector<Detection>& data, float pad_w, float pad_h, float scale, const Size& img_shape);
    
    	// 
    	torch::Tensor xywh2xyxy(const torch::Tensor& x);
    
    	// 
    	vector<vector<Detection>> postProcessing(const torch::Tensor& detections, float pad_w, float pad_h, float scale, const Size& img_shape, float conf_thres, float iou_thres);
    
    	// 
    	Mat Demo(Mat& img, const vector<vector<Detection>>& detections, bool label = true);
    
    public:
    	vector<string> m_class_names;
    	string m_model_path;
    	float m_conf_thres;
    	float m_iou_thres;
    };
    
  4. YoloV5.cpp

    #pragma once
    // C/C++ 自带
    #include <time.h>  
    #include <memory>
    #include <iostream>
    
    // libTorch
    #include <torch/torch.h>
    #include <torch/script.h>
    
    // OpenCV
    #include <opencv2/opencv.hpp>
    #include <opencv2/core/core.hpp>  
    #include <opencv2/highgui/highgui_c.h>
    #include <opencv2/highgui/highgui.hpp>  
    #include <opencv2/imgproc/imgproc.hpp>
    
    // 命名空间
    using namespace cv;
    using namespace std;
    
    // 目标
    enum Det
    {
    	tl_x = 0,
    	tl_y = 1,
    	br_x = 2,
    	br_y = 3,
    	score = 4,
    	class_idx = 5
    };
    
    // 检测
    struct Detection
    {
    	Rect bbox;
    	float score;
    	int class_idx;
    };
    
    
    class YoloV5
    {
    public:
    	// 构造函数
    	YoloV5(string model_path, float conf_thres, float iou_thres);
    
    	// 析构函数
    	~YoloV5();
    
    	// 获取类型名称
    	vector<string> loadClassNames(const string& path);
    
    	// 
    	vector<float> letterBoxImage(const Mat& src, Mat& dst, const Size& out_size);
    
    	// 
    	void tensor2Detection(const at::TensorAccessor<float, 2>& offset_boxes, const at::TensorAccessor<float, 2>& det, vector<Rect>& offset_box_vec, vector<float>& score_vec);
    
    	// 
    	void scaleCoordinates(vector<Detection>& data, float pad_w, float pad_h, float scale, const Size& img_shape);
    
    	// 
    	torch::Tensor xywh2xyxy(const torch::Tensor& x);
    
    	// 
    	vector<vector<Detection>> postProcessing(const torch::Tensor& detections, float pad_w, float pad_h, float scale, const Size& img_shape, float conf_thres, float iou_thres);
    
    	// 
    	Mat Demo(Mat& img, const vector<vector<Detection>>& detections, bool label = true);
    
    public:
    	vector<string> m_class_names;
    	string m_model_path;
    	float m_conf_thres;
    	float m_iou_thres;
    };
    
  5. main.cpp

    #include "YoloV5.h"
    
    int main()
    {
    	// 实例化
    	string video_url = "F:/datasets/TestSample/videos/demo_001.mp4";
    	// string model_path = "F:/projects/C++/study/YoloV5-LibTorch-main/test/yolov5s.cuda.pt";
    	string model_path = "F:/weights/yolov5/head_face.torchscript.pt";
    	float conf_thres = 0.6;
    	float iou_thres = 0.6;
    
    	YoloV5 yolo(model_path, conf_thres, iou_thres);
    	// 判断 是否可以使用 CUDA
    	if (torch::cuda::is_available())
    	{
    		torch::DeviceType device_type;
    		device_type = torch::kCUDA;
    		torch::Device device(device_type);
    		torch::jit::script::Module module;
    		module = torch::jit::load(model_path, device);  //加载模型
    		module.eval();
    
    		// 打开摄像头
    		VideoCapture cap(video_url);
    		cap.set(CAP_PROP_FRAME_WIDTH, 1280);
    		cap.set(CAP_PROP_FRAME_HEIGHT, 720);
    
    		// 读取视频帧率
    		double rate = cap.get(CAP_PROP_FPS);
    		cout << "rate: " << rate << endl;
    
    		//当前视频帧
    		Mat frame;
    
    		// 每一帧之间的延时
    		int delay = 1000 / rate;
    		bool stop(false);
    
    		// 创建窗口
    		cv::namedWindow("Result", cv::WINDOW_AUTOSIZE);
    		while (!stop)
    		{
    			// 开始计时
    			double t = (double)getTickCount();
    			if (!cap.read(frame))
    			{
    				cout << "no video frame" << endl;
    				break;
    			}
    			Mat img = frame;
    
    			// 不收敛
    			torch::NoGradGuard no_grad;
    
    			// 拷贝图像
    			Mat img_input = img.clone();
    
    			vector<float> pad_info = yolo.letterBoxImage(img_input, img_input, cv::Size(640, 640));
    			const float pad_w = pad_info[0];
    			const float pad_h = pad_info[1];
    			const float scale = pad_info[2];
    
    			// BGR -> RGB
    			cvtColor(img_input, img_input, cv::COLOR_BGR2RGB);
    
    			// 归一化需要是浮点类型 normalization 1 / 255
    			img_input.convertTo(img_input, CV_32FC3, 1.0f / 255.0f);
    
    			// 加载图像到设备
    			auto tensor_img = torch::from_blob(img_input.data, { 1, img_input.rows, img_input.cols, img_input.channels() }).to(device_type);
    
    			// BHWC -> BCHW (Batch, Channel, Height, Width)	
    			tensor_img = tensor_img.permute({ 0, 3, 1, 2 }).contiguous();
    			vector<torch::jit::IValue> inputs;
    
    			// 在容器尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造
    			inputs.emplace_back(tensor_img);
    
    			// start = clock();
    			torch::jit::IValue output = module.forward(inputs);
    
    			// 解析结果
    			auto detections = output.toTuple()->elements()[0].toTensor();
    			auto result = yolo.postProcessing(detections, pad_w, pad_h, scale, img.size(), conf_thres, iou_thres);
    
    			// double endtime = (double)(t_stop - t_start) / CLOCKS_PER_SEC;
    			cv::Mat pre_img = yolo.Demo(img, result);
    
    			// 结束计时
    			t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
    
    			// 转换为帧率
    			int fps = int(1.0 / t);
    			putText(pre_img, ("FPS: " + std::to_string(fps)), Point(30, 30), FONT_HERSHEY_COMPLEX, 1, Scalar(0, 255, 255));//输入到帧frame上
    
    			// 显示图像
    			cv::imshow("Result", pre_img);
    
    			// waitKey()函数的作用是刷新imshow()展示的图片
    			if (waitKey(1) == 27)
    			{
    				// 27是键盘摁下esc时,计算机接收到的ascii码值
    				break;
    			}
    		}
    
    		// 释放设备
    		cap.release();
    
    		// 关闭所有窗口
    		destroyAllWindows();
    		return 0;
    	}
    	else
    	{
    		cout << "GPU 不可用!!!" << endl;
    		return -1;
    	}
    }
    

三、YoloV5 & TensorRT加速

1、安装 TensorRT

2、克隆仓库

3、.pt 转 .wts

  • 执行命令

    # 简写
    python gen_wts -w 需要转换的模型路径.pt -o 要保存的模型路径.wts
    python gen_wts -w weights/yolov5s.pt -o weight/yolov5.wts
    
    # 完整写法
    python gen_wts --weight weights/yolov5s.pt -output weight/yolov5.wts
    
  • 执行结果

4、编译C++项目

(1) 修改CMakeList.txt

cmake_minimum_required(VERSION 3.17.1)

# 项目名称
project(yolov5) 

# 指定 opencv 路径
set(OpenCV_DIR "D:\\software\\Opencv\\opencv\\build")
# 指定 TensorRT 路径
set(TRT_DIR "D:\\software\\TensorRT-8.5.3.1\\bin")
# 指定 Dirent 头文件路径
set(Dirent_INCLUDE_DIRS "F:\\projects\\python\\visual_projects\\Yolov5_Tensorrt_Win10-master\\include")

#
add_definitions(-std=c++11)
add_definitions(-DAPI_EXPORTS)
option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads)

# setup CUDA
find_package(CUDA REQUIRED)
message(STATUS "    libraries: ${CUDA_LIBRARIES}")
message(STATUS "    include path: ${CUDA_INCLUDE_DIRS}")
include_directories(${CUDA_INCLUDE_DIRS})
include_directories(${Dirent_INCLUDE_DIRS}) 

# 设置显卡算力
set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS};-std=c++11;-g;-G;-gencode;arch=compute_75;code=sm_75)

####
enable_language(CUDA)  # add this line, then no need to setup cuda path in vs
####
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${TRT_DIR}\\include)

# -D_MWAITXINTRIN_H_INCLUDED for solving error: identifier "__builtin_ia32_mwaitx" is undefined
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Ofast -D_MWAITXINTRIN_H_INCLUDED")

# setup opencv
find_package(OpenCV QUIET
    NO_MODULE
    NO_DEFAULT_PATH
    NO_CMAKE_PATH
    NO_CMAKE_ENVIRONMENT_PATH
    NO_SYSTEM_ENVIRONMENT_PATH
    NO_CMAKE_PACKAGE_REGISTRY
    NO_CMAKE_BUILDS_PATH
    NO_CMAKE_SYSTEM_PATH
    NO_CMAKE_SYSTEM_PACKAGE_REGISTRY
)

message(STATUS "OpenCV library status:")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

include_directories(${OpenCV_INCLUDE_DIRS})
link_directories(${TRT_DIR}\\lib)

add_executable(yolov5 ${PROJECT_SOURCE_DIR}/yolov5.cpp ${PROJECT_SOURCE_DIR}/yololayer.cu ${PROJECT_SOURCE_DIR}/yololayer.h ${PROJECT_SOURCE_DIR}/preprocess.cu) 

target_link_libraries(yolov5 "nvinfer" "nvinfer_plugin")  
target_link_libraries(yolov5 ${OpenCV_LIBS})     
target_link_libraries(yolov5 ${CUDA_LIBRARIES})  
target_link_libraries(yolov5 Threads::Threads)     

(2) 新建 build 文件夹

(4) 打开 CMake

(5) 修改 yololayer.h

(6) 编译

生成成功之后 进入 到 build/Release

将刚才生成的 wts 文件放入到 build/Release 下

转换模型

yolov5 -s head_face.wts head_face.engine l

(7) 生成动态链接库

5、Python 调用

import random
import time
from ctypes import *
from threading import Thread
from datetime import datetime
import multiprocessing
import cv2
import numpy as np
import numpy.ctypeslib as npct
import torch
from PIL import Image, ImageFont, ImageDraw

from deep_sort.deep_sort import DeepSort
from config import cfg


class DetectionTrack:
    def __init__(self, model_path=b'./weights/head_face.engine', dll_path='./yolov5.dll'):
        self.model_path = model_path
        self.yolov5 = CDLL(dll_path, winmode=0)
        self.yolov5.Detect.argtypes = [
            c_void_p,
            c_int,
            c_int,
            POINTER(c_ubyte),
            npct.ndpointer(dtype=np.float32, ndim=2, shape=(50, 6), flags="C_CONTIGUOUS")
        ]
        self.yolov5.Init.restype = c_void_p
        self.yolov5.Init.argtypes = [c_void_p]
        self.yolov5.cuda_free.argtypes = [c_void_p]
        # -------------------- 其他 --------------------
        # 设备信息
        self.device_items = dict()
        # 需要检测的帧
        self.frame_items = dict()
        # 需要跟踪的目标
        self.target_items = dict()
        # 人员信息
        self.person_items = dict()
        # 需要推流的帧
        self.push_frame_items = dict()
        # 推流帧率
        self.min_time = 1000 / cfg.PUSH_CURRENT.RATE - 10
        # 背景
        self.mask_items = dict()
        # 字体
        self.font = ImageFont.truetype(font=cfg.FONT.TYPE, size=cfg.FONT.SIZE)

    # 计算两个框的IOU
    def get_iou(self, boxA, boxB):
        boxA = [int(x) for x in boxA]
        boxB = [int(x) for x in boxB]
        xA = max(boxA[0], boxB[0])
        yA = max(boxA[1], boxB[1])
        xB = min(boxA[2], boxB[2])
        yB = min(boxA[3], boxB[3])
        interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)
        boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
        boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
        iou = interArea / float(boxAArea + boxBArea - interArea)
        return iou

    # 标记人员信息
    def mark_person_info(self, frame, track_ids, person_items):
        try:
            if len(track_ids):
                frame_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
                draw = ImageDraw.Draw(frame_pil)
                for track_id in track_ids:
                    person_item = person_items.get(track_id)
                    if person_item:
                        # 框出目标
                        x1, y1, x2, y2 = person_item.get('head_boxes')
                        draw.rectangle(((x1, y1), (x2, y2)), fill=None, outline=(58, 194, 103), width=2)
                        # if person_item.get('face_boxes'):
                        #     face_x1, face_y1, face_x2, face_y2 = person_item.get('face_boxes')
                        #     draw.rectangle(
                        #         ((face_x1, face_y1), (face_x2, face_y2)),
                        #         fill=None,
                        #         outline=(60, 162, 230),
                        #         width=2
                        #     )
                        # 显示目标名称&人脸
                        face_img = person_item.get('face_img')
                        if face_img is not None:
                            pil_face_img = Image.fromarray(cv2.cvtColor(face_img, cv2.COLOR_BGR2RGBA))
                            text = f'{track_id} {person_item.get("name")}'
                            if person_item.get("name") == '计算中':
                                mask = self.mask_items.get(f'color-7-{len(text)}')
                            elif person_item.get("name") == '陌生人':
                                mask = self.mask_items.get(f'color-1-{len(text)}')
                            else:
                                mask = self.mask_items.get(f'color-4-{len(text)}')
                            if not mask:
                                continue
                            else:
                                frame_pil.paste(pil_face_img, (x1 + 4, y1 - 24), mask=pil_face_img)
                                frame_pil.paste(mask, (x1, y1 - 28), mask=mask)
                                draw.text((x1 + 32, y1 - 22), text, font=self.font, fill=(255, 255, 255))
                frame = cv2.cvtColor(np.array(frame_pil), cv2.COLOR_RGB2BGR)
        except Exception as e:
            print(f'ERROR - {datetime.now()} - 图像标注异常: {e}')
        return frame

    # 抽帧线程
    def draw_frame(self, device_item):
        device_id = device_item.get('id')
        print(f'INFO - {datetime.now()} - 抽帧线程 {device_id} 启动中...')
        # 1. 链接设备
        cap = cv2.VideoCapture(device_item.get('url'))

        # 2. 判断设备是否在线
        if cap.isOpened():
            self.device_items[device_id]['device_status'] = True

        # 3. 开启循环抽帧
        while self.device_items.get(device_id):
            ret = cap.grab()
            if not ret:
                # 尝试重连设备
                print(f'WARNING - {datetime.now()} - 抽帧线程 {device_id} 设备重连中..')
                self.device_items[device_id]['device_status'] = False
                time.sleep(10)
                cap = cv2.VideoCapture(device_item.get('url'))
                # 2. 判断设备是否在线
                if cap.isOpened():
                    self.device_items[device_id]['device_status'] = True
                continue

            if self.frame_items.get(device_id) is None:
                _, frame = cap.retrieve()
                if frame is not None:
                    frame = cv2.resize(frame, (cfg.PUSH_CURRENT.WIDTH, cfg.PUSH_CURRENT.HEIGHT))
                    self.frame_items.update({device_id: frame})
                else:
                    time.sleep(0.04)
            else:
                time.sleep(0.04)
        print(f'INFO - {datetime.now()} - 抽帧线程 {device_id} 退出!!!')

    # 检测线程
    def frame_detect(self, device_item):
        device_id = device_item.get('id')
        status = True
        c_point = self.yolov5.Init(self.model_path)
        print(f'INFO - {datetime.now()} - 检测线程 {device_id} 启动中...')
        # 1. 判断设备是否存在
        while self.device_items.get(device_id) and self.device_items.get(device_id).get('model') == 1:
            # 2. 判断设备是否已经准备完成
            if not self.device_items.get(device_id).get('device_status'):
                time.sleep(3)
                continue
            # 3. 获取帧图并进行检测
            frame = self.frame_items.get(device_id)
            if frame is not None:
                head_boxes = []
                face_boxes = []
                if status:
                    status = False
                    self.frame_items.update({device_id: None})
                    rows, cols = frame.shape[0], frame.shape[1]
                    res_arr = np.zeros((50, 6), dtype=np.float32)
                    self.yolov5.Detect(c_point, c_int(rows), c_int(cols), frame.ctypes.data_as(POINTER(c_ubyte)), res_arr)
                    for bbox in res_arr[~(res_arr == 0).all(1)]:
                        cls = int(bbox[4])
                        score = bbox[5]
                        x, y, w, h = int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3])
                        if x < 0: x = 0
                        if y < 0: y = 0
                        if cls == 1:
                            face_boxes.append([x, y, x + w, y + h, cls, score])
                        else:
                            head_boxes.append([x, y, x + w, y + h, cls, score])
                else:
                    status = True
                    time.sleep(0.04)
                self.target_items.update({
                    device_id: {
                        'frame': frame.copy(),
                        'head_boxes': head_boxes,
                        'face_boxes': face_boxes,
                        'status': True if not status else False
                    }
                })
            else:
                time.sleep(0.04)
        self.yolov5.cuda_free(c_point)
        print(f'INFO - {datetime.now()} - 检测线程 {device_id} 退出!!!')

    # 跟踪线程
    def target_tracker(self, device_item):
        device_id = device_item.get('id')
        print(f'INFO - {datetime.now()} - 跟踪线程 {device_id} 启动中...')

        # 1. 实例化跟踪器
        deepsort = DeepSort(
            cfg.DEEPSORT.REID_CKPT,
            max_dist=cfg.DEEPSORT.MAX_DIST,
            min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE,
            nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP,
            max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE,
            max_age=cfg.DEEPSORT.MAX_AGE,
            n_init=cfg.DEEPSORT.N_INIT,
            nn_budget=cfg.DEEPSORT.NN_BUDGET,
            use_cuda=True
        )

        # 2. 开始跟踪
        track_ids = []
        while self.device_items.get(device_id) and self.device_items.get(device_id).get('model') == 1:
            start_time = time.time() * 1000
            # 3. 判断设备是否已经准备完成
            if not self.device_items.get(device_id).get('device_status'):
                time.sleep(3)
                continue
            bbox_xywh, confs, lbls = [], [], []
            target_item = self.target_items.get(device_id)
            person_items = self.person_items.get(device_id)
            if target_item:
                frame = target_item.get('frame')
                status = target_item.get('status')
                head_boxes = target_item.get('head_boxes')
                face_boxes = target_item.get('face_boxes')
                self.target_items.update({device_id: None})
                if status:
                    if len(head_boxes) == 0:
                        person_items.clear()
                        continue
                    track_ids = []
                    for x1, y1, x2, y2, lbl, conf in head_boxes:
                        obj = [int((x1 + x2) / 2), int((y1 + y2) / 2), x2 - x1, y2 - y1]
                        bbox_xywh.append(obj)
                        confs.append(conf)
                        lbls.append(lbl)
                    xywhs = torch.Tensor(bbox_xywh)
                    confss = torch.Tensor(confs)
                    outputs = deepsort.update(xywhs, confss, frame)
                    for value in list(outputs):
                        x1, y1, x2, y2, track_id = value
                        track_ids.append(track_id)
                        person_item = person_items.get(track_id)
                        if person_item:
                            person_item.update({
                                'head_boxes': [x1, y1, x2, y2],
                                'expire_time': int(time.time() + 60)
                            })
                            name = person_item.get('name')
                            if name in ['计算中', '陌生人']:
                                person_item.update({'face_boxes': None, })
                                for face_x1, face_y1, face_x2, face_y2, face_lbl, face_conf in face_boxes:
                                    if abs(x1 - face_x1) < 50 and abs(y1 - face_y1) < 50:
                                        head_face_iou = self.get_iou(
                                            [x1, y1, x2, y2],
                                            [face_x1, face_y1, face_x2, face_y2]
                                        )
                                        if head_face_iou > cfg.SYSTEM.IOU_THRESHOLD:
                                            person_item.update({
                                                'face_boxes': [face_x1, face_y1, face_x2, face_y2],
                                                'face_img': cv2.resize(
                                                    frame[face_y1:face_y2, face_x1:face_x2],
                                                    (20, 20)
                                                )
                                            })
                                            if random.randint(1, 10) == 5:
                                                person_item.update({'name': '邢兴'})
                                            break
                                        else:
                                            continue
                                    else:
                                        continue
                            else:
                                continue
                        else:
                            person_item = {
                                'head_boxes': [x1, y1, x2, y2],
                                'face_boxes': None,
                                'track_id': track_id,
                                'face_img': None,
                                'face_image_id': None,
                                'name': '计算中',
                                'similarity': 0,
                                'repositoryId': None,
                                'repository_name': None,
                                'health_code': None,
                                'has_info': 1,
                                'front_flag': 0,
                                'last_time': int(time.time() * 1000)
                            }
                        person_items.update({track_id: person_item})
                    self.person_items.update({
                        device_id: person_items
                    })
                else:
                    time.sleep(0.02)
                run_time = time.time() * 1000 - start_time
                cv2.putText(
                    frame,
                    'FPS: {:.2f}'.format(1000 / run_time),
                    (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.5,
                    (0, 255, 0),
                    2
                )
                self.push_frame_items.update({
                    device_id: {'frame': frame, 'track_ids': track_ids, 'person_items': person_items}
                })
            else:
                time.sleep(0.04)
                continue
        print(f'INFO - {datetime.now()} - 跟踪线程 {device_id} 退出!!!')

    # 推流线程
    def push_frame(self, device_item):
        device_id = device_item.get('id')
        winname = f'frame-{device_id}'
        print(f'INFO - {datetime.now()} - 推流线程 {device_id} 启动中...')
        # 1. 判断设备是否存在
        while self.device_items.get(device_id) and self.device_items.get(device_id).get('model') == 1:

            # 2. 判断设备是否已经准备完成
            if not self.device_items.get(device_id).get('device_status'):
                time.sleep(3)
                continue
            # 3. 获取推流数据并进行标注
            push_frame_item = self.push_frame_items.get(device_id)
            if push_frame_item is not None:
                self.push_frame_items.update({device_id: None})
                frame = push_frame_item.get('frame')
                track_ids = push_frame_item.get('track_ids')
                person_items = push_frame_item.get('person_items')
                # 处理帧
                frame = self.mark_person_info(frame, track_ids, person_items)
                cv2.imshow(winname, frame)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break
            else:
                time.sleep(0.04)
        del self.device_items[device_id]
        cv2.destroyWindow(winname)
        print(f'INFO - {datetime.now()} - 推流线程 {device_id} 退出!!!')

    # 入口函数
    def run(self):
        multiprocessing.set_start_method('spawn')
        print(f'INFO - {datetime.now()} - 初始化MASK...')
        face_mask = Image.open(f'media/images/face.png')
        for i in range(1, 8):
            text_mask = Image.open(f'media/images/text-{i}.png')
            circle_mask = Image.open(f'media/images/circle-{i}.png')
            box_mask = Image.open(f'media/images/face-{i}.png')
            for y in range(2, 101):
                w = y * 14 + 28
                text_mask = text_mask.resize(((y - 1) * 14, 28))
                image = Image.new(mode="RGBA", size=(w, 28))
                image.paste(box_mask, (0, 0), mask=box_mask)
                image.paste(face_mask, (0, 0), mask=face_mask)
                image.paste(circle_mask, (w - 28, 0), mask=circle_mask)
                image.paste(text_mask, (28, 0), mask=text_mask)
                self.mask_items.update({f'color-{i}-{y}': image})

        print(f'INFO - {datetime.now()} - 开启设备中...')
        device_items = [
            {
                'id': 1,
                'url': 'rtsp://admin:syyt@123@192.168.1.26',
                'model': 1
            },
            {
                'id': 2,
                'url': 'rtsp://admin:syyt@123@192.168.1.25',
                'model': 1
            },
            {
                'id': 3,
                'url': 'F:/datasets/Videos/1.mp4',
                'model': 1
            }
        ]
        for device_item in device_items:
            self.person_items.update({device_item.get('id'): dict()})
            self.device_items.update({device_item.get('id'): device_item})
            # 启动 抽帧线程
            Thread(target=self.draw_frame, args=(device_item,), daemon=True).start()
            # 启动 检测线程
            Thread(target=self.frame_detect, args=(device_item,), daemon=True).start()
            # 启动 跟踪线程
            Thread(target=self.target_tracker, args=(device_item,), daemon=True).start()
            # 启动 推流线程
            Thread(target=self.push_frame, args=(device_item,), daemon=True).start()
        while self.device_items:
            time.sleep(10)


if __name__ == '__main__':
    DetectionTrack().run()

第四章 OpenCV & dlib

官网地址:http://dlib.net/

一、环境配置

1、下载

所有版本:https://sourceforge.net/projects/dclib/files/dlib/

github地址:https://github.com/davisking/dlib/releases

2、解压

新建 build 目录

3、CMake 编译

二、生成库文件

1、Debug 版本

2、Release 版本

修改文件名为 dlib.lib

三、使用 dlib

1、创建一个空项目

2、配置属性 - Release

3、编写 C++ 源代码

#include <opencv2/opencv.hpp>
#include <dlib/opencv.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing.h>
 
//由于dlib和opencv中有相当一部分类同名,故不能同时对它们使用using namespace,否则会出现一些莫名其妙的问题
//using namespace cv;
//using namespace dlib;
using namespace std;

// 检测图片
void detectionImage(cv::Mat& src)
{
    // 使用 dlib 中的方法获取正面人脸,可以获取多个
    dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();
    vector<dlib::rectangle> faces = detector(dlib::cv_image<dlib::bgr_pixel>(src));

    // 获取人脸特征点
    dlib::shape_predictor predictor;

    // 获取已经训练好的人脸特征模型文件 (http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2)
    dlib::deserialize("F:/weights/dlib/shape_predictor_68_face_landmarks.dat") >> predictor;

    // 画出特征点
    for (int i = 0; i < faces.size(); i++)
    {
        dlib::full_object_detection landmarks = predictor(dlib::cv_image<dlib::bgr_pixel>(src), faces[i]);
        for (int j = 0; j < landmarks.num_parts(); j++)
        {
            cv::circle(src, cv::Point(landmarks.part(j).x(), landmarks.part(j).y()), 2, cv::Scalar(0, 255, 0), -1, cv::LINE_8, 0);
        }
    }

    // 显示图像 
    cv::imshow("src", src);
    cv::waitKey(0);
    cv::destroyAllWindows();
}

// 检测视频
void detectionVideo(const string& url)
{
    // 载入已经训练好的人脸特征模型文件
    cout << "DEBUG - 正在载入人脸 68 个特征点检测模型..." << endl;
    dlib::shape_predictor predictor;
    dlib::deserialize("F:/weights/dlib/shape_predictor_68_face_landmarks.dat") >> predictor;
    cout << "DEBUG - 人脸 68 个特征点检测模型载入完成" << endl;

    // 读取本地文件&摄像头地址
    cv::VideoCapture cap(url);
    if (cap.isOpened())
    {
        cout << "DEBUG - 视频打开成功" << endl;
    }
    else
    {
        cout << "WARNING - 视频打开失败" << endl;
    }

    // 获取视频信息
    int frame_width = cap.get(cv::CAP_PROP_FRAME_WIDTH);	// 宽
    int frame_height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);	// 高
    int count = cap.get(cv::CAP_PROP_FRAME_COUNT);			// 帧数
    double fps = cap.get(cv::CAP_PROP_FPS);					// 帧率
    int fourcc = cap.get(cv::CAP_PROP_FOURCC);				// 编码格式
    cout << "视频宽度: " << frame_width << endl;
    cout << "视频高度: " << frame_height << endl;
    cout << "视频帧数: " << count << endl;
    cout << "视频帧率: " << fps << endl;
    cout << "视频编码: " << fourcc << endl;

    // 使用 dlib 接口创建人脸检测对象
    dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();

    // 循环处理获取每一帧图像
    cv::Mat frame, dst;
    while (true)
    {
        // 抽帧
        cap.read(frame);
        if (frame.empty())
        {
            cout << "WARNING - 视频抽帧失败" << endl;
            break;
        }
        
        // 视频缩放
        cv::resize(frame, frame, cv::Size(480, 270), 0, 0, cv::INTER_AREA);

        // 处理检测到的每个人脸
        vector<dlib::rectangle> faces = detector(dlib::cv_image<dlib::bgr_pixel>(frame));
        
        // 画出特征点
        // cout << "人脸数量: " << faces.size() << endl;
        for (int i = 0; i < faces.size(); i++)
        {
            dlib::full_object_detection landmarks = predictor(dlib::cv_image<dlib::bgr_pixel>(frame), faces[i]);
            for (int j = 0; j < landmarks.num_parts(); j++)
            {
                cv::circle(frame, cv::Point(landmarks.part(j).x(), landmarks.part(j).y()), 2, cv::Scalar(0, 255, 0), -1, cv::LINE_8, 0);
            }
        }

        // 显示图片
        cv::imshow("frame", frame);
        if (cv::waitKey(10) == 27)
        {
            break;
        }
    }
    
    // 释放设备
    cap.release();
    cv::destroyAllWindows();
    cout << "DEBUG - 退出检测程序" << endl;
}

// 入口函数
int main(int argc, char* argv[])
{   
    // 检测图像
    // 读取人脸图像
    // cv::Mat frame = cv::imread("F:/datasets/TestSample/images/demo_01.jpg");
    // detectionImage(frame);

    // 检测视频
    // string url = "rtsp://admin:syyt@123@192.168.1.25";
    string url = "F:/datasets/TestSample/videos/demo_005.mp4";
    detectionVideo(url);
    return 0;
}

4、运行程序

posted @ 2023-06-01 14:22  菜鸟程序员_python  阅读(396)  评论(0)    收藏  举报