OpenCV4快速入门

OpenCV4快速入门

001-图像读取与显示

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

using namespace cv;
using namespace std;

int main(){
	Mat src = imread("G:/OpenCV/opencv笔记所用图片/bandian.jpg",1);		//默认加载彩色图像,0表示GRAY灰度图,1为BGR彩色图,-1表示加载原图(可能是其他类型如HSV等其他空间)

	if (src.empty()){
		cout << "could not load image..." << endl;
		getchar();
		return -1;
	}

	//当我们读取图片太大时,看不到全局,使用窗口函数可以设置大小
	namedWindow("input", WINDOW_AUTOSIZE);	//WINDOW_FREERATIO参数可以调整窗口大小。默认图像为WINDOW_AUTOSIZE显示原图,不能调整大小。
	imshow("input", src);					//若无namedWindow,只有imshow,显示的图像窗口与图片一样大,无法调整窗口大小
											//imshow只能显示8位和浮点型的
	
	waitKey(0);				//opencv自带阻塞函数,0表示一直阻塞,1表示延迟1毫秒后执行下一步
	destroyAllWindows();	//结束程序前将所有窗口销毁
	return 0;
}

quickDemo类的定义(头文件)

quickDemo.h

#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

class quickDemo
{
public:
	void colorSpace_Demo(Mat &image);		//002-色彩空间变换(cvtColor)
	void mat_creation_demo(Mat &image);		//003-图像对象的创建与赋值(m2=m1,clone,copyto)
	void pixel_visit_demo(Mat &image);		//004-图像像素的读写操作(image.at<uchar>(row, col),image.at<Vec3b>(row, col)[0])
	void operators_demo(Mat &image);		//005-图像像素的算术操作(+,-,multiply,divide)
	void trackbar_demo(Mat &image);			//006-trackbar滚动条操作演示-调整图像亮度(add,subtract)
	void trackbar_demo2(Mat &image);		//007-trackbar滚动条操作演示-参数传递与调整亮度与对比度(addWeighted)
	void key_demo(Mat &image);				//008-键盘响应操作(waitKey)
	void color_style_demo(Mat &image);		//009-opencv自带颜色表操作(applyColorMap)
	void bitwise_demo(Mat &image);			//010-图像像素的逻辑操作(位操作,与、或、非)
	void channels_demo(Mat &image);			//011-通道分离与合并(split,merge)
	void inrange_demo(Mat &image);			//012-图像色彩空间转换-变换背景(cvtColor,inRange)
	void pixel_static_demo(Mat &image);		//013-图像像素值统计(minMaxLoc,meanStdDev)
	void drawing_demo(Mat &image);			//014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)
	void random_drawing_demo();				//015-随机数与随机颜色
	void polyline_drawing_demo();			//016-多边形填充与绘制(fillPoly,polylines,drawContours)
	void mouse_drawing_demo(Mat &image);	//017-鼠标操作与响应(setMouseCallback)
	void norm_demo(Mat &image);				//018-图像像素类型转换与归一化(convertTo,normalize)
	void resize_demo(Mat &image);			//019-图像缩放与插值(resize)
	void flip_demo(Mat &image);				//020-图像翻转(flip)
	void rotate_demo(Mat &image);			//021-图像旋转(getRotationMatrix2D,warpAffine)
	void video_demo(Mat &image);			//022-视频文件摄像头使用(VideoCapture capture)
	void video_demo2(Mat &image);			//023-视频处理与保存-帧宽高(capture.get)
	void showHistogram(Mat &image);			//024-图像直方图-绘制直方图(calcHist,normalize,line)
	void histogram_2d_demo(Mat &image);		//025-二维直方图(calcHist,minMaxLoc)
	void histogram_eq_demo(Mat &image);		//026 - 直方图均衡化(equalizeHist)
	void blur_demo(Mat &image);				//027-图像卷积操作-均值模糊(blur)
	void gaussian_blur_demo(Mat &image);	//028-高斯模糊(GaussianBlur)
	void bifilter_demo(Mat &image);			//029-高斯双边模糊(bilateralFilter)
	void face_detection_demo();				//030-案例:实时人脸检测(采用dnn模块)
};

quickDemo类的函数实现

quickDemo.cpp

#include "quickDemo.h"
#include <opencv2/dnn.hpp>		//要使用dnn模块需要引入此头文件

002-色彩空间变换(cvtColor)

//002-色彩空间变换
void quickDemo::colorSpace_Demo(Mat &image){
	if (image.channels()==3)
	{
		Mat gray, hsv;
		cvtColor(image, hsv, COLOR_BGR2HSV);		//可在hsv空间调节亮度通道,再变回BGR空间
		cvtColor(image, gray, COLOR_BGR2GRAY);
		imshow("HSV", hsv);
		imshow("gray image", gray);
		imwrite("G:/OpenCV/opencv笔记所用图片/gray.jpg", gray);
		imwrite("G:/OpenCV/opencv笔记所用图片/hsv.jpg", hsv);
	}else if (image.channels()==1)
	{
		return;
	}

}

003-图像对象的创建与赋值(m2=m1,clone,copyto)

//003-图像对象的创建与赋值
void quickDemo::mat_creation_demo(Mat &image){
	

	Mat m1, m2;
	m1 = image.clone();
	image.copyTo(m2);

	cout << "m1.size :"<<m1.size() << endl;

	//创建空白图像
	Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3);	//创建一个8行8列的,CV_8UC1(单通道无符号整型空白图像).CV_8UC3(三通道的)
	cout << m3 << endl;							//将数组打印出来
	m3 = Scalar(127, 127, 127);					//可以使用此方式给每个通道赋值
	//cout << m3 << endl;							//将数组打印出来
	cout << "width:"<<m3.cols<<",height:"<<m3.rows<<",channels:"<<m3.channels() << endl;
	imshow("创建的图像",m3);				//因为值都为127,故应是一张全部灰色的图像
	m3 = Scalar(255, 0, 0);					//可以使用此方式给每个通道赋值
	imshow("创建的图像2", m3);			//因为值(255,0,0)(B,G,R),故应是蓝色图像

	//Mat m3 = Mat::zeros(Size(8, 8), CV_8UC1);	//此时创建的图像的值初始化为1(当为单通道时全部是1,但3通道不同)
	//cout << m3 << endl;
	//Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3);	//只有在3通道的第1个通道为1,其余为0
	//cout << m3 << endl;

	//当我们使用mat给另外的对象赋值时,相当于将指针重新赋给了另一个对象如下
	//	Mat image2 = image1;
	//当我们修改 image2的值时,也会影响到image1的值,image2就等同于image1.
	//	而使用clone或者copyto时,才将image1的数据完全的复制出来给image2,而不是给指针。
	//	Mat image2 = image1.clone();
	//当我们修改 image2的值时,不会影响到image1的值,只会修改 image2的值。

	//创建空白图像
	Mat m4 = Mat::zeros(Size(400, 400), CV_8UC3);
	m4 = Scalar(255, 0, 0);
	imshow("图像m4", m4);
	Mat m5;
	m5 = m4;
	m5 = Scalar(0, 0, 255);		//此处修改m5的值也会修改m4的值,使用clone或者copyto就不会改变
	imshow("图像m4(m5 = m4)", m4);
	imshow("图像m5", m5);

	//下面这种方式一般在定义卷积核中使用较广
	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0,
		-1, 5, -1,
		0, -1, 0);
}

004-图像像素的读写操作(image.at(row, col),image.at(row, col)[0])

//004-图像像素的读写操作
void quickDemo::pixel_visit_demo(Mat &image){
	int w = image.cols;		//获取列数
	int h = image.rows;		//获取行数
	int dims = image.channels();	//获取通道数

	当通道数不同时,遍历像素的方式也不同(使用数组的方式读取像素)
	//for (int row = 0; row < h; row++){
	//	for (int col = 0; col < w; col++){
	//		if (dims == 1){		//单通道的遍历方式
	//			int pv = image.at<uchar>(row, col);		//将图像row行col列的值赋值给pv
	//			//以下操作具有将图像反差化的功能
	//			image.at<uchar>(row, col) = 255 - pv;	//将255-pv的值重新赋给image图像,image图像中的值就被改变了
	//		}
	//		else if (dims == 3){	//三通道的遍历方式
	//			Vec3b bgr = image.at<Vec3b>(row, col);		//此时一次性的将三通道的值都获取了
	//			//以下操作具有将图像反差化的功能
	//			image.at<Vec3b>(row, col)[0] = 255 - bgr[0];
	//			image.at<Vec3b>(row, col)[1] = 255 - bgr[1];
	//			image.at<Vec3b>(row, col)[2] = 255 - bgr[2];
	//		}
	//	}
	//}

	//当通道数不同时,遍历像素的方式也不同(使用指针的方式读取像素,以下功能相当于上面的功能)
	for (int row = 0; row < h; row++){
		uchar* current_row = image.ptr<uchar>(row);		//得到当前行的指针
		for (int col = 0; col < w; col++){
			if (dims == 1){		//单通道的遍历方式
				int pv = *current_row;		//将图像row行[0]列的值赋值给pv
				*current_row++ = 255 - pv;	//将255-pv的值重新赋给图像row行[0]列,从此开始,指针自加,即列自加,变为[row][1],[row][2]...
			}
			else if (dims == 3){	//三通道的遍历方式
				*current_row++ = 255 - *current_row;	//此处应为当前列的的第【0】个通道
				*current_row++ = 255 - *current_row;	//此处的*current_row不是上方的*current_row,应是当前列的的第【1】个通道
				*current_row++ = 255 - *current_row;	//此处的*current_row不是上方的*current_row,应是当前列的的第【2】个通道
			}
		}
	}

	imshow("像素读写演示", image);
}

005-图像像素的算术操作(+,-,multiply,divide)

//005-图像像素的算术操作
void quickDemo::operators_demo(Mat &image){
	Mat dst;
	dst = image + Scalar(50, 50, 50);	//每个通道加50,图像会变亮
	imshow("加法操作", dst);			
	dst = image - Scalar(50, 50, 50);	//每个通道-50,图像会变暗
	imshow("减法操作", dst);

	Mat m = Mat::zeros(image.size(), image.type());//创建一个和image大小相同,类型相同,值为0的图片
	m = Scalar(2, 2, 2);
	dst = image /m;	//两张大小、类型相同的图片除法操作
	imshow("除法操作", dst);

	//使用opencv自带的api函数进行加减乘除操作
	//两张大小、类型相同的图片乘法操作不能使用(dst = image *m;),会报错	
	multiply(image, m, dst);		//使用multiply可以实现两图像的乘法运算
	imshow("multiply操作", dst);
	add(image, m, dst);		//使用add可以实现两图像的加法运算
	imshow("add操作", dst);
	subtract(image, m, dst);		//使用subtract可以实现两图像的减法运算
	imshow("subtract操作", dst);
	divide(image, m, dst);		//使用divide可以实现两图像的除法运算
	imshow("divide操作", dst);

	//通过对图像像素点的操作实现算术运算
	//当通道数不同时,遍历像素的方式也不同(使用数组的方式读取像素)
	Mat dst2 = Mat::zeros(image.size(),image.type());		//创建一个背景图像
	int w = image.cols;		//获取列数
	int h = image.rows;		//获取行数
	int dims = image.channels();	//获取通道数
	for (int row = 0; row < h; row++){
		for (int col = 0; col < w; col++){
			if (dims == 1){		//单通道的遍历方式
				int p1 = image.at<uchar>(row, col);		//将图像row行col列的值赋值给p1
				int p2 = m.at<uchar>(row, col);
				//以下操作具有将图像反差化的功能
				dst2.at<uchar>(row, col) = saturate_cast<uchar>( p1 + p2);	//将p1和p2的像素点相加赋值给dst2图像(当前行列)
				//saturate_cast<uchar>(p1 + p2)可以将p1和p2相加得到的值限制在0~255之间,防止值超过此区间
			}
			else if (dims == 3){	//三通道的遍历方式
				Vec3b p1 = image.at<Vec3b>(row, col);		//image的像素点赋值给p1(当前行列)
				Vec3b p2 = m.at<Vec3b>(row, col);			//m的像素点赋值给p2(当前行列)
				//将p1和p2的像素点相加赋值给dst2图像(当前行列)
				dst2.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);
				dst2.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);
				dst2.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[2]);
			}
		}
	}
	imshow("自定义像素点相加", dst2);

}

006-trackbar滚动条操作演示-调整图像亮度(add,subtract)

//006-create-trackbar("Value Bar")的回调函数(此处使用add等算术操作函数调整亮度)
Mat src;			//此处使用全局变量来传递数据src到回调函数中(也可以使用void*来在参数中传递数据)
static void on_track(int lightness, void *userdata){
	Mat m = Mat::zeros(src.size(), src.type());		//创建背景图像
	m = Scalar(lightness, lightness, lightness);	//给此图像赋值
	Mat dst;
	add(src, m, dst);								//两图像相加并输出dst
	//subtract(src, m, dst);						//两图像相减并输出dst
	imshow("亮度调整", dst);						//显示在窗口上
}

//006-trackbar滚动条操作演示-调整图像亮度
void quickDemo::trackbar_demo(Mat &image){
	namedWindow("亮度调整", WINDOW_AUTOSIZE);
	int maxValue = 255;
	int lightness = 50;
	src = image;
	createTrackbar("Value Bar", "亮度调整", &lightness, maxValue,on_track);	//创建亮度调整的滑动条
	//参数1:滑动条名称,参数2:窗口名称,参数3:滑动条初始值,参数4:滑动条最大范围,参数5:回调函数,参数6:传递数据(万能指针)

	on_track(50, &image);	//调用一次回调函数,令初始时就有显示(可以不调用),
						//此处应注意,第二个参数在上面传递数据时不能为0,若初始化时指针为0,就会报错(空指针在传递数据时不要存在)
}

007-trackbar滚动条操作演示-参数传递与调整亮度与对比度(addWeighted)

//007-create-trackbar("Value Bar")的回调函数(此处使用addWeighted调整亮度)
static void on_track2_1(int lightness, void *image){
	Mat src = *(Mat*)image;			//(Mat*)image将指针image强转Mat类型的,再使用*将里面的值取出,又是赋值操作,此时src就是image
	//此处使用的是createbar自带的传递数据功能,也可以使用全局变量来传递数据

	Mat m = Mat::zeros(src.size(), src.type());		//创建背景图像
	m = Scalar(lightness, lightness, lightness);	//给此图像赋值
	Mat dst;
	addWeighted(src, 1.0, m, 0, lightness, dst);		//两张图以一定的权重相加(src*1+m*0+lightness),此处m图像无用,只对src图像进行加法操作
	imshow("亮度与对比度调整", dst);						//显示在窗口上
}

//007-create-trackbar("Value Bar")的回调函数(此处使用addWeighted调整对比度)
static void on_track2_2(int contrast_value, void *image){
	Mat src = *(Mat*)image;			//(Mat*)image将指针image强转Mat类型的,再使用*将里面的值取出,又是赋值操作,此时src就是image
	//此处使用的是createbar自带的传递数据功能,也可以使用全局变量来传递数据

	Mat m = Mat::zeros(src.size(), src.type());		//创建背景图像
	Mat dst;
	addWeighted(src, contrast_value, m, 0, 0, dst);		//两张图以一定的权重相加(src*contrast_value+m*0+0),此处m图像无用,只对src图像进行乘法运算,增加其对比度
	imshow("亮度与对比度调整", dst);						//显示在窗口上
}

//007-trackbar滚动条操作演示-参数传递与调整亮度与对比度
void quickDemo::trackbar_demo2(Mat &image){
	namedWindow("亮度与对比度调整", WINDOW_AUTOSIZE);
	int maxValue = 255;
	int lightness = 50;
	createTrackbar("Value Bar", "亮度与对比度调整", &lightness, maxValue, on_track2_1, &image);	//创建亮度调整的滑动条
	//参数1:滑动条名称,参数2:窗口名称,参数3:滑动条初始值,参数4:滑动条最大范围,参数5:回调函数,参数6:传递数据(万能指针)

	int contrast_value = 1.2;
	createTrackbar("Contrast Bar", "亮度与对比度调整", &contrast_value, 5, on_track2_2, &image);//创建对比度调整的滑动条

	on_track2_1(50, &image);	//调用一次回调函数,令初始时就有显示(可以不调用),此处应注意,第二个参数在上面传递数据时不能为0,若初始化时指针为0,就会报错(空指针在传递数据时不要存在)
	//on_track2_2(50, &image);	 
}

008-键盘响应操作(waitKey)

//008-键盘响应操作
void quickDemo::key_demo(Mat &image){
	Mat dst=Mat::zeros(image.size(),image.type());
	while (true){		//在此循环不断监听键盘操作
		char c = waitKey(100);		//停顿100毫秒,waitKey(100)返回值为100毫秒内键盘按键的ASCII码值。
		//cout << c << endl;			//输出waitekey接收到的键值

		//在此无限循环中加入退出机制(按esc退出循环)
		if ((int)c == 27){			//此处将c类型强转,或者int c = waitKey(100)
			break;		//27为退出键esc的数值
		}

		//判断输入键值,进行一定操作
		if ((int)c == 49){			//判断当按下为1时,或者不强转直接c=='1'
			cout << "you enter key #"<<c << endl;
			cvtColor(image, dst, COLOR_BGR2GRAY);		//将其变为灰度图像
		}
		if ((int)c == 50){			//判断当按下为2时
			cout << "you enter key #" << c << endl;
			cvtColor(image, dst, COLOR_BGR2HSV);		//将其变为HSV图像
		}
		if (c == '3'){			//此处也可以使用此种方式判断,或者(int)c == 51
			cout << "you enter key #" << c << endl;
			dst = Scalar(50, 50, 50);
			add(image, dst, dst);
		}
		imshow("键盘响应", dst);
	}
}

009-opencv自带颜色表操作(applyColorMap)

//009-opencv自带颜色表操作
void quickDemo::color_style_demo(Mat &image){
	//COLORMAP_AUTUMN = 0, //!< ![autumn](pics/colormaps/colorscale_autumn.jpg)
	//	COLORMAP_BONE = 1, //!< ![bone](pics/colormaps/colorscale_bone.jpg)
	//	COLORMAP_JET = 2, //!< ![jet](pics/colormaps/colorscale_jet.jpg)
	//	COLORMAP_WINTER = 3, //!< ![winter](pics/colormaps/colorscale_winter.jpg)
	//	COLORMAP_RAINBOW = 4, //!< ![rainbow](pics/colormaps/colorscale_rainbow.jpg)
	//	COLORMAP_OCEAN = 5, //!< ![ocean](pics/colormaps/colorscale_ocean.jpg)
	//	COLORMAP_SUMMER = 6, //!< ![summer](pics/colormaps/colorscale_summer.jpg)
	//	COLORMAP_SPRING = 7, //!< ![spring](pics/colormaps/colorscale_spring.jpg)
	//	COLORMAP_COOL = 8, //!< ![cool](pics/colormaps/colorscale_cool.jpg)
	//	COLORMAP_HSV = 9, //!< ![HSV](pics/colormaps/colorscale_hsv.jpg)
	//	COLORMAP_PINK = 10, //!< ![pink](pics/colormaps/colorscale_pink.jpg)
	//	COLORMAP_HOT = 11, //!< ![hot](pics/colormaps/colorscale_hot.jpg)
	//	COLORMAP_PARULA = 12

	//opencv3.0的版本中的色彩表只有12中类型,在opencv4.1中则高达19种
	//通过定义一个数组(里面的值实际为整数数字),来在后面实现图片的轮流播放
	int colorMap[] = {
		COLORMAP_AUTUMN, //!< ![autumn](pics/colormaps/colorscale_autumn.jpg)
		COLORMAP_BONE, //!< ![bone](pics/colormaps/colorscale_bone.jpg)
		COLORMAP_JET, //!< ![jet](pics/colormaps/colorscale_jet.jpg)
		COLORMAP_WINTER, //!< ![winter](pics/colormaps/colorscale_winter.jpg)
		COLORMAP_RAINBOW, //!< ![rainbow](pics/colormaps/colorscale_rainbow.jpg)
		COLORMAP_OCEAN, //!< ![ocean](pics/colormaps/colorscale_ocean.jpg)
		COLORMAP_SUMMER, //!< ![summer](pics/colormaps/colorscale_summer.jpg)
		COLORMAP_SPRING, //!< ![spring](pics/colormaps/colorscale_spring.jpg)
		COLORMAP_COOL, //!< ![cool](pics/colormaps/colorscale_cool.jpg)
		COLORMAP_HSV, //!< ![HSV](pics/colormaps/colorscale_hsv.jpg)
		COLORMAP_PINK, //!< ![pink](pics/colormaps/colorscale_pink.jpg)
		COLORMAP_HOT, //!< ![hot](pics/colormaps/colorscale_hot.jpg)
		COLORMAP_PARULA
	};
	Mat dst;
	int index = 0;
	while (true){
		int c = waitKey(1000);
		if (c == 27){
			break;
		}
		applyColorMap(image, dst, colorMap[index % 12]);		//index%12保证此数值一直在12之内
		cout << "第" << index % 12 << "种颜色风格" << endl;
		index++;
		imshow("颜色风格", dst);

		//如果输入键值为s保存当前图像
		string imagename = "颜色风格-" + to_string(index % 12)+".jpg";
		char s = waitKey(1000);
		//String类下valueOf可将任何类型转换成String类型
		if (s == 's'){
			imwrite(imagename, dst);
		}
	}
}

010-图像像素的逻辑操作(位操作,与、或、非)


//010-图像像素的逻辑操作(位操作,与、或、非)
void quickDemo::bitwise_demo(Mat &image){
	Mat m1 = Mat::zeros(Size(256,256), CV_8UC3);
	Mat m2 = Mat::zeros(Size(256, 256), CV_8UC3);
	rectangle(m1, Rect(100,100,80,80),Scalar(255,255,0),-1);	//绘制矩形并填充
	rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), -1);	//绘制矩形并填充
	imshow("m1", m1);
	imshow("m2", m2);
	Mat dst;
	bitwise_and(m1, m2, dst);		//m1与m2的交集Scalar(255,255,0) && Scalar(0, 255, 255)==Scalar(0, 255, 0),只剩下G通道的值不为0
	imshow("m1和m2的与操作", dst);
	bitwise_or(m1, m2, dst);		//m1与m2的交集Scalar(255,255,0) || Scalar(0, 255, 255)==Scalar(255, 255, 255),BGR通道值全为255,故此区域为白色
	imshow("m1和m2的或操作", dst);
	bitwise_xor(m1, m2, dst);		//m1与m2的交集Scalar(255,255,0) XOR Scalar(0, 255, 255)==Scalar(255,0, 255)
	imshow("m1和m2的异或操作", dst);
	bitwise_not(image, dst);			//对image取反操作(使用~image也可以直接进行取反操作)
	imshow("对image的非操作", dst);
}

011-通道分离与合并(split,merge)

//011-通道分离与合并
void quickDemo::channels_demo(Mat &image){
	
	//通道分离的方式有两种
	Mat aChannels[3];				//利用数组分离,此处必须为3,否则会报错(split(image, aChannels); )

	vector<Mat> mv;					//利用vector对象分离
	//src为要分离的Mat对象  
	split(image, mv);              //split分离后的是单通道图像
	imshow("蓝色单通道", mv[0]);
	imshow("绿色单通道", mv[1]);
	imshow("红色单通道", mv[2]);

	Mat dst;
	merge(mv,dst);		//对vector对象的合并(和原图像相同三通道)
	imshow("使用vector合并成原图", dst);

	//可以对单通道进行某些操作后再进行合并
	Mat mzero = Mat::zeros(mv[0].size(), mv[0].type());	//创建一个和mv[0]单通道大小类型相同的,值为0的单通道图像,通道合并时使用
	aChannels[0] = mv[0].clone();				//使用clone不会影响到mv[0]
	aChannels[1] = mzero.clone();
	aChannels[2] = mzero.clone();
	merge(aChannels, 3, dst);			//对数组的合并(三通道)
	imshow("蓝色三通道", dst);

	aChannels[0] = mzero.clone();
	aChannels[1] = mv[1].clone();
	aChannels[2] = mzero.clone();
	merge(aChannels, 3, dst);			//对数组的合并(三通道),参数2:合并数组的个数
	imshow("绿色三通道", dst);

	aChannels[0] = mzero.clone();
	aChannels[1] = mzero.clone();
	aChannels[2] = mv[2].clone();
	merge(aChannels, 3, dst);			//对数组的合并(三通道)
	imshow("红色三通道", dst);

	//进行通道的混合,可以对多个图像混合
	int from_to[] = { 0, 2,		//表示将第1个图像的[0]通道数据,传递给第2个图像的[2]通道
					  1, 1,		//表示将第1个图像的[1]通道数据,传递给第2个图像的[1]通道
					  2, 0,};	//表示将第1个图像的[2]通道数据,传递给第2个图像的[0]通道
	mixChannels(&image, 1, &dst, 1, from_to, 3);	//此处的dst在上面的变换已经是3通道且大小类型与image相同
	//参数1:输入图像地址,
	//参数2:输入图像的个数1,
	//参数3:输出图像地址,输出图像个数1,
	//参数4:设置输入矩阵的通道对应输出矩阵的通道,
	//参数5:共有几组输入输出通道的关系,此处是3组
	imshow("mixChannels混合通道", dst);

	Mat dst2 = Mat::zeros(image.size(), image.type());	//此处dst2的大小类型需和image相同,否则执行mixChannels会报错
	int from_to2[] = {0, 0};
	mixChannels(&image, 1, &dst2, 1, from_to2, 1); //此处也能显示蓝色,和上面的蓝色单通道颜色相同
	imshow("蓝色混合通道", dst2);
}

012-图像色彩空间转换-变换背景(cvtColor,inRange)

//012-图像色彩空间转换-背景变换(cvtColor,inRange)
void quickDemo::inrange_demo(Mat &image){
	Mat hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);		//hsv色彩空间表https://blog.csdn.net/leo_888/article/details/88284251
	//通过hsv空间表,确定出绿色的范围(H:35-77,S:43-255,V:46-255),使用inrange将此范围提取出来
	Mat mask;
	inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), mask);	//此处提取的是绿色区域部分
	imshow("inRange_mask", mask);		

	Mat redback = Mat::zeros(image.size(), image.type());
	redback = Scalar(40, 40, 200);			//将此创建的背景图片变为红色
	bitwise_not(mask, mask);			//此处对mask取反得到人物的区域
	imshow("bitwise_not_mask", mask);		
	image.copyTo(redback, mask);		//此处将image图像在mask中不为0的点拷贝到redback上,mask为0的区域就不要了
	imshow("redback", redback);		//显示背景为红色,前景为人的图像
}

013-图像像素值统计(minMaxLoc,meanStdDev)

//013-图像像素值统计(minMaxLoc,meanStdDev)
void quickDemo::pixel_static_demo(Mat &image){
	double minv, maxv;				//最小值,最大值
	Point minLoc, maxLoc;			//最小值位置,最大值位置
	vector<Mat> mv;
	split(image, mv);
	for (int i = 0; i < mv.size(); i++){
		minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat());	//输入矩阵应为单通道
		cout << "No.channels:" << i << ",min value:" << minv << ",max value:" << maxv << endl;
	}
	Mat mean, stddev;
	meanStdDev(image, mean, stddev);	//参数1:输入矩阵(1-4通道),参数2:计算均值,参数3:计算方差,参数4:mask(指定ROI区域)
	cout << "mean:" << mean <<endl<< "stddeve:" << stddev << endl;
	cout << "mean-type:" << mean.type() << endl << "stddeve-type:" << stddev.type() << endl;
	//mean和stddev的type都是6,故矩阵类型为CV_64F,数据是double类型的,所以取值时应at<double>
	//CV_8U=ucahr,CV_8S=cahr,CV_32S=int,CV_32F=float,CV_64F=double
	
	//提取单通道的均值及均方差(mean是个单通道的矩阵(使用imagewatch查看其状态),提取其值使用)
	double meanB = mean.at<double>(0, 0);		//一定要注意at<>中的取值类型,不对就会报错,此处用imagewatch调试,看不能使用int,只能使用double类型
	cout << "meanB:" << meanB << endl;
	double meanG = mean.at<double>(1, 0);		//一定要注意at<>中的取值类型,不对就会报错,此处用imagewatch调试,看不能使用int,只能使用double类型
	cout << "meanG:" << meanG << endl;
	double meanR = mean.at<double>(2, 0);		//一定要注意at<>中的取值类型,不对就会报错,此处用imagewatch调试,看不能使用int,只能使用double类型
	cout << "meanR:" << meanR << endl;
}

014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)

//014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)
void quickDemo::drawing_demo(Mat &image){
	Mat bg = Mat::zeros(image.size(), image.type());
	//绘制矩形
	Rect rect;
	rect.x = 100;
	rect.y = 50;
	rect.width = 200;
	rect.height = 200;
	rectangle(bg, rect, Scalar(0, 0, 255), -1,8);	//参数4为-1时表示填充
	//绘制圆
	circle(bg, Point(300, 250), 15, Scalar(255, 0, 0), 2,8);
	//绘制线
	line(bg, Point(100, 50), Point(300, 250), Scalar(0, 255, 0), 2,LINE_AA);	//LINE_AA反锯齿(更平滑)
	//绘制椭圆
	RotatedRect rrt;
	rrt.center = Point(200,100);
	rrt.size = Size(100, 200);
	rrt.angle = 45.0;					//椭圆角度
	ellipse(bg, rrt, Scalar(0, 255, 255), 2, 8);
	imshow("绘制演示", bg);

	Mat dst;
	addWeighted(image, 0.7, bg, 0.3, 0, dst);
	imshow("addWeighted演示", dst);
}

015-随机数与随机颜色

//015-随机数与随机颜色
void quickDemo::random_drawing_demo(){
	Mat canavas = Mat::zeros(Size(512, 512), CV_8UC3);
	int w = canavas.cols;
	int h = canavas.rows;
	RNG rng(1234);
	while (true)
	{
		int c = waitKey(100);
		if (c==27){					//当按下ESC键时退出循环
			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);

		canavas = Scalar(0, 0, 0);		//每次循环将图像变为黑色(刷新图像,可以实现每次绘制一条线,若注释则绘制多条线)
		line(canavas, Point(x1, y1), Point(x2, y2), Scalar(b, g, r), 1, LINE_AA);
		imshow("随机绘制演示", canavas);
	}
}

016-多边形填充与绘制(fillPoly,polylines,drawContours)


//016-多边形填充与绘制(fillPoly,polylines,drawContours)
void quickDemo::polyline_drawing_demo(){
	Mat canavas = Mat::zeros(Size(512, 512), CV_8UC3);
	Point p1(100, 100);
	Point p2(350, 100);
	Point p3(450, 280);
	Point p4(320, 450);
	Point p5(80, 400);
	vector<Point> pts;
	pts.push_back(p1);
	pts.push_back(p2);
	pts.push_back(p3);
	pts.push_back(p4);
	pts.push_back(p5);
	
	//fillPoly(canavas, pts, Scalar(255, 255, 0), 8);			//此函数专用填充多边形(此种形式opencv3.0一直报错(可以使用其重载),opencv4可以实现)
	//imshow("fillPoly多边形填充", canavas);

	polylines(canavas, pts, true, Scalar(0, 0, 255), 2, 8,0);	//此函数无法将参数5变为-1来填充,会报错
	//参数1:输入输出图像矩阵,参数2:输入点集,参数3:是否封闭,参数4:颜色,参数5:线宽,参数6:线型,参数7:偏置	
	imshow("polylines多边形绘制", canavas);
	
	//通过点集绘制轮廓
	vector<vector<Point>> contours;
	contours.push_back(pts);
	drawContours(canavas, contours, -1, Scalar(255, 0, 0), -1);	//参数5位-1时填充
	imshow("drawContours多边形绘制", canavas);
}

017-鼠标操作与响应(setMouseCallback)

//017-定义鼠标事件的回调函数
Point sp(-1, -1);
Point ep(-1, -1);
Mat temp;		//用于保存原图,已完成对之前画有矩形的图片的擦除(所以拖动时永远是最新的矩形了)
static void on_draw(int event, int x, int y, int flags, void *userdata){

	Mat image = *((Mat*)userdata);
	if (event==EVENT_LBUTTONDOWN)		//当鼠标按下时,得到初始位置
	{
		sp.x = x;
		sp.y = y;
		cout << "start point:" << sp << endl;
	}
	else if (event == EVENT_LBUTTONUP)							//当鼠标抬起时,得到结束位置
	{
		ep.x = x;
		ep.y = y;
		//cout << "end point:" << sp << endl;
		//绘制矩形窗
		int dx = ep.x - sp.x;
		int dy = ep.y - sp.y;
		if ( dx>0 && dy>0 )
		{
			Rect box(sp.x, sp.y, dx, dy);
			temp.copyTo(image);
			Mat imageBox = image(box);
			imshow("ROI区域", imageBox);	//此处直接截取ROI区域
			rectangle(image, box, Scalar(0, 0, 255), 1, 8, 0);
			imshow("鼠标绘制", image);		//绘制完成后要更新

			//结束后,为下一次做准备
			sp.x = -1;
			sp.y = -1;
		}		
	}
	else if (event == EVENT_MOUSEMOVE){			//当鼠标移动过程中实时显示矩形大小
		if (sp.x>0 && sp.y>0)		//只有当鼠标按下了,才能有所响应
		{
			ep.x = x;
			ep.y = y;

			if (ep.x>image.cols || ep.y>image.rows)
			{
				cout << "无法截取边界,请重新截取" << endl;
				getchar();
				exit(1);		//当绘制的矩形超出图像坐标时,要退出,否则会报错
			}
			//cout << "end point:" << sp << endl;
			//绘制矩形窗
			int dx = ep.x - sp.x;
			int dy = ep.y - sp.y;
			if (dx > 0 && dy > 0)
			{
				Rect box(sp.x, sp.y, dx, dy);
				temp.copyTo(image);
				rectangle(image, box, Scalar(0, 0, 255), 1, 8, 0);
				imshow("鼠标绘制", image);		//绘制完成后要更新
			}
		}

	}

}

//017-鼠标操作与响应(setMouseCallback)
void quickDemo::mouse_drawing_demo(Mat &image){
	namedWindow("鼠标绘制", WINDOW_AUTOSIZE);
	setMouseCallback("鼠标绘制", on_draw,(void*)(&image));
	imshow("鼠标绘制", image);
	temp = image.clone();
}

018-图像像素类型转换与归一化(convertTo,normalize)

//018-图像像素类型转换与归一化(convertTo,normalize)
void quickDemo::norm_demo(Mat &image){
	//一般在深度学习等预处理图像时将数据归一化
	//将image变为float数据类型
	Mat dst;
	cout <<"image.type():"<< image.type() << endl;
	image.convertTo(image, CV_32F);
	imshow("浮点型图像", image);						//浮点型的图像要想正常显示必须是0-1之间的,否则在0-255之间的浮点数无法正常显示
	cout << "image.type():" << image.type() << endl;
	//CV_8UC3=16,CV_32FC3=21
	//CV_8U=ucahr,CV_8S=cahr,CV_32S=int,CV_32F=float,CV_64F=double

	//将0-255的数据值范围归一化为0-1,要在0-1数据应为浮点型(https://blog.csdn.net/DP323/article/details/80273996)
	normalize(image, dst, 1.0, 0, NORM_MINMAX);//输入数组是浮点型的,输出的和输入的数值类型相同,只是取值范围不同(通过imagewatch查看)
												//image若不转类型,之前是无符号整型,当归一化0-1时,数值都是0.几,就会成为黑色图像
	//参数1:输入数组
	//参数2::输出数组,支持原地运算
	//参数3:range normalization模式的最小值
	//参数4:range normalization模式的最大值,不用于norm normalization(范数归一化)模式。
	//参数5::归一化的类型,可以有以下的取值:
	//NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
	//NORM_INF : 此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C - 范数(绝对值的最大值)
	//NORM_L1 : 归一化数组的L1 - 范数(绝对值的和)
	//NORM_L2 : 归一化数组的(欧几里德)L2 - 范数
	cout << "dst.type():" << dst.type() << endl;
	imshow("图像数据归一化", dst);
}

019-图像缩放与插值(resize)

//019-图像缩放与插值
void quickDemo::resize_demo(Mat &image){
	//在深度学习中可能需要将其转换成一定大小的图片,并归一化到0-1
	Mat zoomin, zoomout;
	int h = image.rows;
	int w = image.cols;
	resize(image, zoomin, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);
		/*参数1:输入图像
		参数2:输出图像,形态和输入图相同,当dsize不等于0,输出图尺寸会和dsize相同, 当dsize等于0,输出图尺寸会由输入图尺寸、fx、fy计算而得
		参数3:输出图像大小,若为零,则参数4、5不能和其同时为0,dsize = Size(round(fxsrc.cols), round(fysrc.rows))
		参数4:水平缩放比例,当输入为0时,fx = (double)dsize.width / src.cols
		参数5:垂直缩放比例, 当输入为0时,fy = (double)dsize.height / src.rows
		参数6:插值方式,如下
		INTER_NEAREST - 最近邻插值
		INTER_LINEAR - 线性插值(默认),此种方法要比最近邻插值更平滑
		INTER_AREA - 区域插值
		INTER_CUBIC - 三次样条插值
		INTER_LANCZOS4 - Lanczos插值*/
	imshow("zoomin", zoomin);
	resize(image, zoomout, Size(w *1.5, h * 1.5), 0, 0, INTER_LINEAR);
	imshow("zoomout", zoomout);
}

020-图像翻转(flip)

//020-图像翻转(flip)
void quickDemo::flip_demo(Mat &image){
	Mat dst;
	flip(image, dst, 0);
	/*参数1:输入图像
	  参数2:输出图像
	  参数3:>0: 沿x - 轴翻转, 0 : 沿y - 轴翻转, <0 : x、y轴同时翻转*/
	imshow("图像翻转", dst);
}

021-图像旋转(getRotationMatrix2D,warpAffine)

//021-图像旋转(getRotationMatrix2D,warpAffine)
void quickDemo::rotate_demo(Mat &image){
	Mat dst, M;
	int w = image.cols;
	int h = image.rows;
	//获得旋转矩阵M
	M = getRotationMatrix2D(Point(w/2,h/2), 45, 1.0);	//参数1;确定原图像的旋转中心,参数2:旋转角度,参数3:缩放因子(1.0表示大小不变)

	//对原图像进行旋转操作1(输出图像大小不变,但旋转的一部分无法全部显示)
	warpAffine(image, dst, M, image.size(), INTER_LINEAR,0,Scalar(0,0,255));
	  /*.src: 输入图像
		.dst : 输出图像,尺寸由dsize指定,图像类型与原图像一致
		.M : 2X3的变换矩阵
		.dsize : 指定图像输出尺寸
		.flags : 插值算法标识符,有默认值INTER_LINEAR
		.borderMode : 边界像素模式,有默认值BORDER_CONSTANT
		.borderValue : 边界颜色取值,有默认值Scalar()即0*/
	imshow("旋转演示1", dst);

	//旋转操作2,改变输出旋转图像大小,使其将旋转后的图像全部显示
	//M矩阵使用imagewatch查看其内容,2行3列,返回矩阵https://blog.csdn.net/limit_ing/article/details/61952187
	//
	//确定新的旋转图像大小及偏移
	double cos = abs(M.at<double>(0, 0));	//得到角度a的余弦值cos(a)	,abs()求元素的绝对值 ,CV_64F=double
	double sin = abs(M.at<double>(0, 1));	//得到角度a的正弦值sin(a)
	int nw = cos*w + sin*h;					//获取新的图像宽度
	int nh = sin*w + cos*h;					//获取新的图像宽度
	M.at<double>(0, 2) = M.at<double>(0, 2) + (nw / 2 - w/ 2);	//更改旋转矩阵M中的(0,2)处的值,此处含义类似更改旋转中心宽度
	M.at<double>(1, 2) = M.at<double>(1, 2) + (nh/ 2 - h / 2);	//更改旋转矩阵M中的(1,2)处的值,此处含义类似更改旋转中心高度
	warpAffine(image, dst, M, Size(nw,nh), INTER_LINEAR, 0, Scalar(255, 0, 255));
	imshow("旋转演示2", dst);
}

022-视频文件摄像头使用(VideoCapture capture)

//022-视频文件摄像头使用(VideoCapture)
void quickDemo::video_demo(Mat &image){
	//VideoCapture capture(0);	//初始化一个capture对象用来获取视频摄像头,0表示获取电脑本机摄像头
	VideoCapture capture("G:\\OpenCV\\opencv所用视频\\wuDao.avi");	//从指定路径中读取视频
	if (!capture.isOpened())
	{
		std::cout << "Read video Failed !" << std::endl;
		getchar();
		return;
	}
	Mat frame;
	while (true)
	{
		capture.read(frame);	//将视频读取到Mat矩阵中
		if (frame.empty())
		{
			break;
		}
		imshow("frame",frame);
		flip(frame, frame, 1);//电脑摄像头的视频有镜像,将其再反转一下
		//TODO:do something...(可以在此处做一些对图像的操作)
		cvtColor(frame, frame, COLOR_BGR2GRAY);
		imshow("灰度", frame);

		//当按键为ESC时退出循环
		int c = waitKey(1);
		if (c==27)
		{
			break;
		}
	}
	//资源释放
	capture.release();


	另外一种读取视频文件方式(https://blog.csdn.net/u010368556/article/details/79186992)
	//cv::VideoCapture capture;
	//capture.open("G:\\OpenCV\\opencv所用视频\\wuDao.avi");

	//if (!capture.isOpened())
	//{
	//	std::cout << "Read video Failed !" << std::endl;
	//	getchar();
	//	return;
	//}

	//cv::Mat frame;
	//cv::namedWindow("video test");

	//int frame_num = capture.get(cv::CAP_PROP_FRAME_COUNT);
	//std::cout << "total frame number is: " << frame_num << std::endl;

	//for (int i = 0; i < frame_num - 1; ++i)
	//{
	//	capture >> frame;		//第二种方式  
	//	//capture.read(frame); 
	//	imshow("video test", frame);
	//	if (cv::waitKey(30) == 'q')
	//	{
	//		break;
	//	}
	//}

	//cv::destroyWindow("video test");
	//capture.release();
}

023-视频处理与保存-帧宽高(capture.get)

//023-视频处理与保存-帧宽高(capture.get)(未完成)
void quickDemo::video_demo2(Mat &image){
	//https://blog.csdn.net/cv_jason/article/details/54619505
	VideoCapture capture(0);	//初始化一个capture对象用来获取视频摄像头,0表示获取电脑本机摄像头
	//VideoCapture capture("G:\\OpenCV\\opencv所用视频\\wuDao.avi");	//从指定路径中读取视频
	if (!capture.isOpened())
	{
		std::cout << "Read video Failed !" << std::endl;
		getchar();
		return;
	}
	
	//set方式可以设置视频宽高,但如果相机不支持此种分辨率输出set也是无法取的
	int frame_width = capture.get(CAP_PROP_FRAME_WIDTH);		//获取视频帧的宽
	int frame_height = capture.get(CAP_PROP_FRAME_HEIGHT);		//获取视频帧的高
	int frame_count = capture.get(CAP_PROP_FRAME_COUNT);		//获取视频总帧数
	int fps = capture.get(CAP_PROP_FPS);			//获取视频FPS(FPS表示每秒处理多少帧画面,处理速度越快,性能越好,FPS一种衡量标准)
	
	cout << "frame width:" << frame_width << endl;
	cout << "frame height:" << frame_height << endl;
	cout << "frame count:" << frame_count << endl;
	cout << "fps:" << fps << endl;

	int codec = capture.get(CAP_PROP_FOURCC);			//获取视频文件格式
	//cout << "codec:" << codec << endl;					//文件格式为10进制的
	//char s[20];
	//sprintf(s, "%x", codec);						//将获取的codec转换成16进制的字符串
	//printf("视频文件格式16进制:%s\n", s);				//将获取的codec以16进制的形式输出

	//直接输出视频编码格式
	cout << "视频文件格式:" << char(codec&0xFF) << char((codec>>8)&0xFF) << char((codec>>16)&0xFF) << char((codec>>24)&0xFF) << endl;
	//&是位操作符,10进制codec的二进制数与16进制0xFF的二进制数进行与操作,而codec的最低8位被保留了下来,其余高位全部变为0,此时再对截断后的8位char就得到字符
	//(11111111 11111111 11111111 10000001) & (0x00000000 00000000 00000000 11111111) = 00000000 00000000 00000000 10000001
	//(codec>>8)&0xFF对codec二进制的第2个高8位进行截断
	//(codec>>16)&0xFF对codec二进制的第3个高8位进行截断
	//(codec>>24)&0xFF对codec二进制的第4个高8位进行截断

	//视频保存大小与获得的视频大小相同(此处使用的opencv4)
	fps = 30;				//当为打开摄像头设备时,此处fps一定要赋值,否则录制的视频无法打开
	VideoWriter writer;
	writer.open("G:\\OpenCV\\opencv所用视频\\wu4.avi", writer.fourcc('M', 'J', 'P', 'G'), fps, Size(frame_width, frame_height), true);	//https://blog.csdn.net/Day_upon/article/details/85991445
	
	//以下编码格式在OpenCV4中都可以运行
	//CV_FOURCC('M','J','P','G')  注意此种格式保存的视频一直无法打开(在opencv3下)
	//CV_FOURCC('P', 'I', 'M', '1') = MPEG - 1 codec(无法打开)(在opencv3下)
	//	CV_FOURCC('M', 'J', 'P', 'G') = motion - jpeg codec(无法打开)(在opencv3下)
	//	CV_FOURCC('M', 'P', '4', '2') = MPEG - 4.2 codec(保存的avi可以打开)(在opencv3下)
	//	CV_FOURCC('D', 'I', 'V', '3') = MPEG - 4.3 codec(保存的avi可以打开)(在opencv3下)
	//	CV_FOURCC('D', 'I', 'V', 'X') = MPEG - 4 codec(可以打开)(在opencv3下)
	//	CV_FOURCC('U', '2', '6', '3') = H263 codec(未测试)
	//	CV_FOURCC('I', '2', '6', '3') = H263I codec(未测试)
	//	CV_FOURCC('F', 'L', 'V', '1') = FLV1 codec(无法打开)

	Mat frame;
	while (true){
		capture.read(frame);	//将视频读取到Mat矩阵中
		if (frame.empty()){
			break;
		}
		imshow("frame", frame);		
		//flip(frame, frame, 1);//电脑摄像头的视频有镜像,将其再反转一下,当为MJPG编码格式时无需翻转
		//TODO:do something...(可以在此处做一些对图像的操作)
		/*cvtColor(frame, frame, COLOR_BGR2GRAY);
		imshow("灰度", frame);*/

		//opencv3版本一直保存失败(原因已经明确)
		writer.write(frame);					//保存视频(opencv只能处理视频不处理音频,处理大小有限制,不要超过2G)
		//writer << frame;
		//当按键为ESC时退出循环
		int c = waitKey(1000/30);
		if (c == 27)
		{
			break;
		}
	}
	//资源释放
	capture.release();
	writer.release();
}

024-图像直方图-绘制直方图(calcHist,normalize,line)

//024-图像直方图(calcHist,normalize,line)
void quickDemo::showHistogram(Mat &image){

	//cvtColor(image, image, COLOR_BGR2GRAY);
	//imshow("gray", image);
	if (image.channels() == 3)
	{
		//三通道分离
		vector<Mat> bgr_plane;
		split(image, bgr_plane);
		//定义参数变量
		const int channels[1] = { 0 };
		const int bins[1] = { 256 };
		float hranges[2] = { 0, 255 };
		const float* ranges[1] = { hranges };
		Mat b_hist;
		Mat g_hist;
		Mat r_hist;
		//计算Blue,green,red通道的直方图
		calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
		calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
		calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
		//参数1;输入图像地址,参数2:1张图片(指针可以指向多个图),参数3:0表示一个通道,参数4:mask使用Mat()空计算整张图像,
		//参数5:输出,参数6:1维,参数7:bins(256个灰度级别),参数8;ranges直方图范围

		//用mv来接收计算得到直方图数据,在后面的y坐标绘制中会使用到(防止b_hist等中途被修改)
		Mat mv[3];
		mv[0] = b_hist.clone();		//在后面的y坐标显示会用到此
		mv[1] = g_hist.clone();
		mv[2] = r_hist.clone();

		//opencv的从数据到图像需要自己绘制
		//显示直方图
		int hist_w = 512;
		int hist_h = 400;
		int bin_w = cvRound((double)hist_w / bins[0]);		//cvRound():返回跟参数最接近的整数值,即四舍五入;
		Mat histImage = Mat::zeros(408, 560, 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());
		//绘制直方图曲线(在一个mat图像上使用line绘制)
		for (int i = 1; i < bins[0]; i++){
			line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
				Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
			line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
				Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(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);
		}

		//绘制x坐标
		for (int i = 0; i < bins[0]; i++){
			if ((i) % 15 == 0){
				//绘制刻度线
				line(histImage, Point(bin_w*(i), 396),
					Point(bin_w*(i), 390), Scalar(255, 255, 0), 2, 8, 0);
				//绘制数字
				string stri = to_string(i);
				putText(histImage, stri, Point(bin_w*(i), 405), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
				//在opencv3下是CV_FONT_HERSHEY_COMPLEX,而在opencv4就是FONT_HERSHEY_COMPLEX
			}
		}

		//绘制y坐标(在右侧绘制)
		//先计算yb_hist中的最大值
		double minv[3], maxv[3];				//最小值,最大值
		Point minLoc, maxLoc;			//最小值位置,最大值位置
		double allmax = 0;
		for (int i = 0; i < 3; i++){
			//y_hist在此使用
			minMaxLoc(mv[i], &minv[i], &maxv[i], &minLoc, &maxLoc, Mat());	//只针对单通道图像
			allmax += maxv[i];
		}
		//此处使用三通道直方图最大值的平均值作为纵坐标
		double averMax = (allmax / 3);
		//将最大值与图像的高求比例
		int yb_h = cvRound(averMax / (double)hist_h);
		for (int j = 1; j < hist_h; j++){
			if ((j) % 20 == 0){
				//绘制刻度线
				line(histImage, Point(511, hist_h - j),
					Point(517, hist_h - j), Scalar(255, 255, 0), 2, 8, 0);
				//绘制数字
				string stri = to_string(yb_h*j);
				putText(histImage, stri, Point(517, hist_h - j), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
			}
		}

		//显示直方图
		namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
		imshow("Histogram Demo", histImage);
	}
	else if (image.channels()==1)
	{
		//定义参数变量
		const int channels[1] = { 0 };
		const int bins[1] = { 256 };
		float hranges[2] = { 0, 255 };
		const float* ranges[1] = { hranges };
		Mat hist;
		//计算Blue,green,red通道的直方图
		calcHist(&image, 1, 0, Mat(), hist, 1, bins, ranges);
		//参数1;输入图像地址,参数2:1张图片(指针可以指向多个图),参数3:0表示一个通道,参数4:mask使用Mat()空计算整张图像,
		//参数5:输出,参数6:1维,参数7:bins(256个灰度级别),参数8;ranges直方图范围

		//将y_hist复制一下,在后面绘制y坐标时使用(防止中间hist值有修改)
		Mat y_hist = hist.clone();

		//opencv的从数据到图像需要自己绘制
		//显示直方图
		int hist_w = 512;
		int hist_h = 400;
		int bin_w = cvRound((double)hist_w / bins[0]);		//cvRound():返回跟参数最接近的整数值,即四舍五入;
		Mat histImage = Mat::zeros(408, 560, CV_8UC3);
		
		//归一化直方图数据(三个通道计算的最小最大值范围不同,将其归一化到一个范围之间(画布高内),显示的时候不会超出画布范围)
		normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
		//绘制直方图曲线(在一个mat图像上使用line绘制)
		for (int i = 1; i < bins[0]; i++){
			line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
				Point(bin_w*(i), hist_h - cvRound(hist.at<float>(i))), Scalar(12, 23, 200), 2, 8, 0);
		}

		//绘制x坐标
		for (int i = 0; i < bins[0]; i++){
			if ((i) % 15 == 0){
				//绘制刻度线
				line(histImage, Point(bin_w*(i), 396),
					Point(bin_w*(i), 390), Scalar(255, 255, 0), 2, 8, 0);
				//绘制数字
				string stri = to_string(i);
				putText(histImage, stri, Point(bin_w*(i), 405), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
			}
		}

		//绘制y坐标(在右侧绘制)
		//先计算yb_hist中的最大值
		double minv, maxv;				//最小值,最大值
		Point minLoc, maxLoc;			//最小值位置,最大值位置
		double allmax = 0;
		//y_hist在此使用
		minMaxLoc(y_hist, &minv, &maxv, &minLoc, &maxLoc, Mat());	//只针对单通道图像		
		//将最大值与图像的高求比例
		int yb_h = cvRound(maxv / (double)hist_h);
		for (int j = 1; j < hist_h; j++){
			if ((j) % 20 == 0){
				//绘制刻度线
				line(histImage, Point(511, hist_h - j),
					Point(517, hist_h - j), Scalar(255, 255, 0), 2, 8, 0);
				//绘制数字
				string stri = to_string(yb_h*j);
				putText(histImage, stri, Point(517, hist_h - j), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
			}
		}

		//显示直方图
		namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
		imshow("Histogram Demo", histImage);
	}

}

025-二维直方图(calcHist,minMaxLoc)

//025-二维直方图(calcHist,minMaxLoc)
void quickDemo::histogram_2d_demo(Mat &image){
	//2D直方图
	Mat hsv, hs_hist;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	int hbins = 30, sbins = 32;
	int hist_bins[] = { hbins, sbins };
	float h_range[] = { 0, 180 };
	float s_range[] = { 0, 256 };
	const float* hs_ranges[] = { h_range, s_range };
	int hs_channels[] = { 0, 1 };
	calcHist(&hsv, 1, hs_channels, Mat(), hs_hist, 2, hist_bins, hs_ranges, true, false);
	//图像地址,1张图像,求前两个通道,对整张图像mask计算,输出结果hs_hist,2维结果,第一通道取值范围hist_bins,第二通道取值范围hs_ranges

	double maxVal = 0;
	minMaxLoc(hs_hist, 0, &maxVal, 0, 0);
	int scale = 10;
	Mat hist2d_image = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);
	//此处绘制的是灰度值
	for (int h = 0; h < hbins; h++){
		for (int s = 0; s < sbins; s++){
			float binVal = hs_hist.at<float>(h, s);
			int intensity = cvRound(binVal * 255 / maxVal);
			rectangle(hist2d_image, Point(h*scale, s*scale),
				Point((h + 1)*scale - 1, (s + 1)*scale - 1),
				Scalar::all(intensity),
				-1);
		}
	}
	imshow("H-S Histogram", hist2d_image);
	applyColorMap(hist2d_image, hist2d_image, COLORMAP_JET);	//将上面的图像结果用彩色的显示
	imshow("H-S Histogram2", hist2d_image);
}

026 - 直方图均衡化(equalizeHist)

//026 - 直方图均衡化(equalizeHist)
void quickDemo::histogram_eq_demo(Mat &image){
	//对灰度图像进行均衡化
	Mat gray;
	//cvtColor(image, gray, COLOR_BGR2GRAY);
	//imshow("gray", gray);

	//对彩色图像的亮度进行均衡化处理
	Mat dst;
	if (image.channels() == 1){			//如果是单通道图像
		equalizeHist(image, dst);	//直接进行直方图均衡化
		imshow("灰度图像均衡化演示", dst);
	}
	else if (image.channels() == 3){		//如果是三通道图像
		Mat YUV;
		cvtColor(image, YUV, COLOR_BGR2YUV);	//先将其转换成YUV空间(里面包含亮度通道)

		//先进行通道分离,将三通道分离成3个单通道
		Mat aChannels[3];				//此处必须为3,否则会报错
		//src为要分离的Mat对象  
		split(YUV, aChannels);              //利用数组分离 

		//Mat aY = aChannels[0];			//此处对应单通道Y(亮度)
		//Mat aU = aChannels[1];			//此处对应单通道U(色差)
		//Mat aV = aChannels[2];			//此处对应单通道V(色差)

		//对亮度通道进行直方图均衡化
		equalizeHist(aChannels[0], aChannels[0]);

		//合并通道并将值赋给YUV
		merge(aChannels, 3, YUV);		//合并通道

		//将YUV空间再次转换成BGR
		cvtColor(YUV, dst, COLOR_YUV2BGR);
		imshow("彩色图像HSV均衡化演示", dst);
	}
}

027-图像卷积操作-均值模糊(blur)

//027-图像卷积操作-均值模糊(blur)
void quickDemo::blur_demo(Mat &image){
	Mat dst;
	blur(image, dst, Size(3, 3), Point(-1, -1));
	//输入图像,输出图像,卷积核大小,锚点为中心,边缘处理方式默认
	imshow("图像模糊", dst);
	blur(image, dst, Size(15, 1), Point(-1, -1));
	imshow("图像模糊-水平方向", dst);
	blur(image, dst, Size(1, 15), Point(-1, -1));
	imshow("图像模糊-垂直方向", dst);
}

028-高斯模糊(GaussianBlur)

//028-高斯模糊(GaussianBlur)
void quickDemo::gaussian_blur_demo(Mat &image){
	Mat dst;
	GaussianBlur(image, dst, Size(5, 5),15);	//默认情况下,sigmax等于sigmay,只设置x的话;
			//当Size(0,0)时,也会有模糊的效果(高斯模糊第3个参数卷积核大小必须是奇数,若为偶数会出错)
			//当Size(0,0)时会通过sigma反算卷积核大小,若Size不为0,就会使用size的大小
	//输入图像,输出图像,卷积核大小,sigmax,sigmay
	imshow("高斯模糊", dst);
	GaussianBlur(image, dst, Size(0, 0), 15);	//sigma对卷积核的影响非常大,若设置Size(0,0),模糊会很大
	imshow("高斯模糊2", dst);
}

029-高斯双边模糊(bilateralFilter)

//029-高斯双边模糊(bilateralFilter)
void quickDemo::bifilter_demo(Mat &image){
	Mat dst;
	bilateralFilter(image, dst, 0, 100, 20);
	//参数解释:
	//	.InputArray src : 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。
	//	.OutputArray dst : 输出图像,和原图像有相同的尺寸和类型。
	//	. int d : 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
	//	. double sigmaColor : 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有月宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
	//	. double sigmaSpace : 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d > 0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
	//	. int borderType = BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT
	imshow("高斯双边模糊", dst);
}

030-案例:实时人脸检测(采用dnn模块)

//030-案例:实时人脸检测(采用dnn模块)
void quickDemo::face_detection_demo(){
	string root_dir = "D:/OpenCV/opencv-4.4.0-vc14_vc15/opencv/sources/samples/dnn/face_detector/";//此处最后要加入/否则会报错
	dnn::Net net = dnn::readNetFromTensorflow(root_dir+"opencv_face_detector_uint8.pb",root_dir+"opencv_face_detector.pbtxt");		//读取神经网络(模型本质是tensflow的模型),读取模型和配置文件
	//VideoCapture capture("G:/OpenCV/opencv所用视频/data_src.mp4");	//从指定路径中读取视频
	VideoCapture capture(0);
	if (!capture.isOpened())
	{
		std::cout << "Read video Failed !" << std::endl;
		getchar();
		return;
	}
	Mat frame;
	while (true)
	{
		capture.read(frame);	//将视频读取到Mat矩阵中
		if (frame.empty())
		{
			break;
		}

		Mat blob = dnn::blobFromImage(frame,1.0,Size(300,300),Scalar(104,177,123),false, false);		//这是一个输入,一个张量,blobFromImage可以转换,scalefactor1.0表示还在0-255的色彩空间,若0.0384,就变成0-1了;
		//在dnn下的models.yml文件有默认取值,不进行通道交换,不进行剪切
		//默认得到的数据是浮点类型的数据

		net.setInput(blob);		//input后就可以获取数据了	blob就是NCHW(N多少个,C通道数,H高度,W宽度)
		Mat probs = net.forward();	//进行推理		
		Mat detectionMat(probs.size[2], probs.size[3], CV_32F, probs.ptr<float>());
		//检测已完成
		//解析结果
		for (int i = 0; i < detectionMat.rows; i++) {
			//(解析7个值,前两个类型,和index,第三个是得分,得分越高越可能是人脸,将其取出,也是个float数据)
			float confidence = detectionMat.at<float>(i, 2);
			if (confidence > 0.5) {				//默认得分大于0.5它就是人脸了
				int x1 = static_cast<int>(detectionMat.at<float>(i, 3)*frame.cols);//预测的值是0-1之间的数,只有乘宽度才会变成真实的
				int y1 = static_cast<int>(detectionMat.at<float>(i, 4)*frame.rows);
				int x2 = static_cast<int>(detectionMat.at<float>(i, 5)*frame.cols);//预测的值是0-1之间的数,只有乘宽度才会变成真实的
				int y2 = static_cast<int>(detectionMat.at<float>(i, 6)*frame.rows);
				//此时就得到矩形4个点的宽高了
				Rect box(x1, y1, x2-x1, y2 - y1);
				rectangle(frame, box, Scalar(0, 0, 255), 2, 8, 0);		//绘制矩形得到人脸检测
			}
		}


		imshow("人脸检测演示", frame);
		//flip(frame, frame, 1);//电脑摄像头的视频有镜像,将其再反转一下
		//TODO:do something...(可以在此处做一些对图像的操作)

		//当按键为ESC时退出循环
		int c = waitKey(1);
		if (c == 27)
		{
			break;
		}
	}
	//资源释放
	capture.release();
}

主程序调用类

main.cpp

#include "quickDemo.h"

int main(){
	Mat src = imread("G:/OpenCV/opencv笔记所用图片/bandian.jpg",1);		//默认加载彩色图像,0表示GRAY灰度图,1为BGR彩色图,-1表示加载原图(可能是其他类型如HSV等其他空间)

	if (src.empty()){
		cout << "could not load image..." << endl;
		getchar();
		return -1;
	}

	//当我们读取图片太大时,看不到全局,使用窗口函数可以设置大小
	namedWindow("input", WINDOW_AUTOSIZE);	//WINDOW_FREERATIO参数可以调整窗口大小。默认图像为WINDOW_AUTOSIZE显示原图,不能调整大小。
	imshow("input", src);					//若无namedWindow,只有imshow,显示的图像窗口与图片一样大,无法调整窗口大小
											//imshow只能显示8位和浮点型的

	quickDemo qd;
	//qd.colorSpace_Demo(src);			//002-色彩空间变换
	//qd.mat_creation_demo(src);		//003-图像对象的创建与赋值
	//qd.pixel_visit_demo(src);			//004-图像像素的读写操作
	//qd.operators_demo(src);			//005-图像像素的算术操作
	//qd.trackbar_demo(src);			//006-trackbar滚动条操作演示-调整图像亮度
	//qd.trackbar_demo2(src);			//007-trackbar滚动条操作演示-参数传递与调整亮度与对比度
	//qd.key_demo(src);					//008-键盘响应操作
	//qd.color_style_demo(src);			//009-opencv自带颜色表操作
	//qd.bitwise_demo(src);				//010-图像像素的逻辑操作(位操作,与、或、非)
	//qd.channels_demo(src);			//011-通道分离与合并
	//qd.inrange_demo(src);				//012-图像色彩空间转换-背景变换(cvtColor,inRange)
	//qd.pixel_static_demo(src);		//013-图像像素值统计(minMaxLoc,meanStdDev)
	//qd.drawing_demo(src);				//014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)
	//qd.random_drawing_demo();			//015-随机数与随机颜色
	//qd.polyline_drawing_demo();		//016-多边形填充与绘制(fillPoly,polylines,drawContours)
	//qd.mouse_drawing_demo(src);		//017-鼠标操作与响应(setMouseCallback)
	//qd.norm_demo(src);				//018-图像像素类型转换与归一化(convertTo,normalize)
	//qd.resize_demo(src);				//019-图像缩放与插值(resize)
	//qd.flip_demo(src);				//020-图像翻转(flip)
	//qd.rotate_demo(src);				//021-图像旋转(getRotationMatrix2D,warpAffine)
	//qd.video_demo(src);				//022-视频文件摄像头使用(VideoCapture)
	//qd.video_demo2(src);				//023-视频处理与保存-帧宽高(capture.get)
	//qd.showHistogram(src);			//024-图像直方图-绘制直方图(calcHist,normalize,line)
	//qd.histogram_2d_demo(src);		//025-二维直方图(calcHist,minMaxLoc)
	//qd.histogram_eq_demo(src);		//026 - 直方图均衡化(equalizeHist)
	//qd.blur_demo(src);				//027-图像卷积操作-均值模糊(blur)
	//qd.gaussian_blur_demo(src);		//028-高斯模糊(GaussianBlur)
	//qd.bifilter_demo(src);			//029-高斯双边模糊(bilateralFilter)
	qd.face_detection_demo();			//030-案例:实时人脸检测(采用dnn模块)
	
	waitKey(0);				//opencv自带阻塞函数,0表示一直阻塞,1表示延迟1毫秒后执行下一步
	destroyAllWindows();	//结束程序前将所有窗口销毁
	return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾名招财

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值