OpenCV学习笔记(一)

opencv是计算机视觉和机器学习常用的软件库之一,支持多个平台,对多种语言提供接口,我的学习环境是Win10+VS2019+C++

安装与配置

先上opencv官网下载你需要的版本,然后安装到你想要安装的位置

安装好(其实是解压)是下面这个样子。

之后我们需要配置环境变量才能使用

path里添加的路径是opencv可执行文件的路径,打开你解压好的OpenCV文件夹,依次选择build—>x64—>vc15—>bin,此时的路径就是要添加的路径

然后我们要VS能使用opencv的库,还需要对项目进行配置

打开项目的属性页,在包含目录中加入opencv文件夹下的build->include的目录,以及其中的opencv和opencv2文件夹的路径

在库目录中加入opencv文件夹下的build-> x64->vc15->lib文件夹的路径

在附加库目录中也加上这个路径

最后将lib文件夹里的所有lib文件加入附加依赖项中,如果是opencv3.x的话,应该就两个文件opencv_world341d.lib和opencv_world341.lib,中间的341每个版本不同,有d的是debug时用的,没d的是release时用的

至此我们就可以使用opencv来写代码了,如果有问题可以看一下项目是不是设置为X64的

输入输出

我们通过imread函数来读取图像,imshow函数来显示图像,下面的代码将图像的三个通道的值统一为它们的平均值,将他们从彩色图变成了灰度图,threshold为阈值,实现了图像的二值化,即非黑即白

#include 
#include//包含头文件
using namespace cv;//添加命名空间
using namespace std;

int main()
{
	Mat srcMat = imread("D:/壁纸/3.jpg");//读取图像,保存在Mat中
	int height = srcMat.rows;//图像的行数,即图像高度
	int width = srcMat.cols;//图像的列数,即图像宽度
	for (int j = 0; j(j, i)[0] + srcMat.at(j, i)[1] + srcMat.at(j, i)[2]) / 3;//Vec3b即Vec,[0-2]依次为BGR而非RGB
			average = average > threshold ? 255 : 0;
			srcMat.at(j, i)[0] = srcMat.at(j, i)[1] = srcMat.at(j, i)[2] = average;
		}

	imshow("src", srcMat);//输出图像,第一个参数为标题
	waitKey(0);//如果是0表示一直显示图像,直到任意键按下,如果是x则表示x毫秒后关闭
	return 0;
}

深层复制与浅层复制

Mat类包括了一个矩阵的数据,以及矩阵的头(指向矩阵的地址),直接赋值和拷贝构造为浅层复制,只复制了矩阵头,矩阵的数据是共享的,即新的Mat和老的Mat实际上是同一个矩阵,对哪个进行修改,都会同时变化。如果要实现深层的复制,有两个函数clone()copyTo()

clone()会将矩阵的所有信息(包括头部和数据部分)都拷贝到目标矩阵中,也就是说并不会共享数据。 
copyTo()会将矩阵的数据部分拷贝到目的矩阵,且在拷贝之前会调用creat()函数。

下面的代码可以对比深层复制和浅层复制的区别

#include 
#include
using namespace cv;
using namespace std;

void imgaverage(Mat& srcMat)
{
	int height = srcMat.rows;
	int width = srcMat.cols;
	for (int j = 0; j < height; j++)
		for (int i = 0; i < width; i++)
		{
			//uchar threshold = 100;
			uchar average = (srcMat.at(j, i)[0] + srcMat.at(j, i)[1] + srcMat.at(j, i)[2]) / 3;
			//average = average > threshold ? 255 : 0;
			srcMat.at(j, i)[0] = srcMat.at(j, i)[1] = srcMat.at(j, i)[2] = average;
		}
}

int main()
{
	Mat srcMat = imread("D:/壁纸/3.jpg");
	Mat deepMat, shallowMat;
	deepMat = srcMat.clone();
	shallowMat = srcMat;
	imgaverage(srcMat);
	imshow("deep", deepMat);
	imshow("shallow", shallowMat);
	waitKey(0);
	return 0;
}

通道分离

分离只需要一个函数split(),函数原型为

void split(InputArray m, OutputArrayOfArrays mv);

m是输入图像,mv是输出的数组,一般我们用vecor

#include 
#include
#include
using namespace cv;
using namespace std;



int main()
{
	Mat srcMat = imread("D:/壁纸/3.jpg");
	vector channels;
	split(srcMat, channels);
	Mat B = channels[0];
	Mat G = channels[1];
	Mat R = channels[2];
	imshow("R", R);
	imshow("G", G);
	imshow("B", B);
	waitKey(0);
	return 0;
}

摄像头调用

VideoCapture是opencv处理视频的一个类,如果是0表示从摄像头读取视频,如果是视频文件路径则是读取视频文件,可以直接构造时读取,也可以用open()函数来读取,两者用法是差不多的。

VideoCapture有一个get函数,可以读取视频的信息,以下属性分别代表0-18

视频捕获属性 仅支持相机含义
cv::CAP_PROP_POS_MSEC 视频文件中的当前位置(毫秒)或视频捕获时间戳
cv::CAP_PROP_POS_FRAMES 从零开始下一帧索引
cv::CAP_PROP_POS_AVI_RATIO视频中的相对位置(范围为0.1到1.0)
cv::CAP_PROP_FRAME_WIDTH视频帧的像素宽度
cv::CAP_PROP_FRAME_HEIGHT视频帧的像素高度
cv::CAP_PROP_FPS录制视频的帧速率
cv::CAP_PROP_FOURCC四个字符代码只是编解码
cv::CAP_PROP_FRAME_COUNT视频文件中的帧总数
cv::CAP_PROP_FORMAT返回的Mat对象的格式(例如CV_8UC3)
cv::CAP_PROP_MODE表示捕捉模式:值是特定于正在使用的视频后端(例如,DC1394)
cv::CAP_PROP_BRIGHTNESS 相机的亮度设置(支持时)
cv::CAP_PROP_CONTRAST相机的对比度设置(支持时)
cv::CAP_PROP_SATURATION 相机的饱和度设置(支持时)
cv::CAP_PROP_HUE 相机色调设置(支持时)
cv::CAP_PROP_GAIN相机的增益设置(支持时)
cv::CAP_PROP_EXPOSURE相机曝光设置(支持时)
cv::CAP_PROP_CONVERT_RGB如果非零,捕获的图像将被转换为具有三个通道
cv::CAP_PROP_WHITE_BALANCE相机的白平衡设置(支持时)
cv::CAP_PROP_RECTIFICATION立体相机整流标志

只需要每隔一个极短的时间显示一帧的图片即可实现播放视频的效果

#include 
#include
#include
using namespace cv;
using namespace std;



int main()
{
	VideoCapture cap(0);

	/*if (!cap.isOpened())
	{
		cout << "老子打不开" << endl;
		return -1;
	}*/

	double fps = cap.get(CAP_PROP_FPS);
	cout << "fps" << fps << endl;
	while (1)
	{
		Mat frame;
		cap >> frame;//等价于cap.read(frame),即从中读取帧图像
		imshow("frame",frame);
		waitKey(30);
	}
	return 0;
}

绘图

画线

void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
  • img:输入图,线会画在上面。
  • pt1:线的起点。
  • pt2:线的終点。
  • color:线的顏色。
  • thickness:线的厚度。
  • lineType:通道型态,可输入8、4、CV_AA: 8->8通道连结。 4->4通道连结。 CV_AA->消除锯齿(antialiased line),消除显示器画面线边缘的凹凸锯齿。

画圆

void circle(Mat& img, Point center, int radius, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
  • img:输入图,圆会画在上面。
  • center:圆心。
  • radius:圆半径。
  • color:圆形的颜色。
  • thickness:圆形的边线宽度,输入负值或CV_FILLED代表填满圆形。
  • lineType:通道型态,可输入8、4、CV_AA: 8->8通道连结。 4->4通道连结。 CV_AA->消除锯齿(antialiased line),消除显示器画面线边缘的凹凸锯齿。

画矩形

void rectangle(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
  • img:输入图,矩形会画在上面。
  • pt1:矩形顶点。
  • pt2:矩形顶点,pt1的对角边
  • color:矩形的颜色。
  • thickness:矩形的边线宽度,输入负值或CV_FILLED代表填满矩形。
  • lineType:通道型态,可输入8、4、CV_AA: 8->8通道连结。 4->4通道连结。 CV_AA->消除锯齿(antialiased line),消除显示器画面线边缘的凹凸锯齿。

画任意曲线

先计算出曲线上每个点的坐标用vector存每个点,然后使用polylines函数,该函数主要用来绘制多边形,而任意曲线都可以用无限条边的多边形来代替

void polylines(Mat& img, const Point** pts, const int* npts, int ncontours, bool isClosed, const Scalar& color, int thickness=1, intlineType=8, int shift=0)
  • img:输入图,多边形会画在上面。
  • pts:包含多边形各个曲线点的阵列。
  • npts:包含多边形各曲线顶点数目的阵列。
  • ncontours:曲线数。
  • isClosed:是否为封闭的多边形。
  • color:多边形的颜色。
  • thickness:多边形的边线宽度,输入负值或CV_FILLED代表填满多边形。
  • lineType:通道型态,可输入8、4、CV_AA: 8->8通道连结。 4->4通道连结。 CV_AA->消除锯齿(antialiased line),消除显示器画面线边缘的凹凸锯齿。
#include 
#include
#include
using namespace cv;
using namespace std;



int main()
{
	Mat srcMat = imread("D:/壁纸/3.jpg");
	circle(srcMat, Point(srcMat.cols >> 1, srcMat.rows >> 1), 100, Scalar(0, 0, 255), 10);
	line(srcMat, Point(srcMat.cols >> 1, 0), Point(srcMat.cols >> 1, srcMat.rows >> 1), Scalar(0, 255, 0), 10);
	rectangle(srcMat, Rect(0, 0, 100, 200), Scalar(255.0,0), 10);
	vector v;
	for (int i = -100; i < 100; i++)
	{
		int x = i + srcMat.cols >> 1;
		int y = i * i/20 + srcMat.cols >> 1;
		v.push_back(Point(x, y));
	}
	polylines(srcMat, v, false, Scalar(255, 255, 0), 5);
	imshow("circle", srcMat);
	waitKey(0);
	return 0;
}

直方图计算

图像直方图是用一表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。可以借助观察该直方图了解需要如何调整亮度分布的直方图。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此,一张较暗图片的图像直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。计算机视觉邻域常借助图像直方图来实现图像的二值化。

对于三通道图像的直方图计算,我们是转化为灰度图进行计算,或者分成三个通道分别计算。计算直方图只需统计每个像素值的出现次数。为了方便比较我们需要归一化,即除以像素的总数。

直方图的计算opencv有个 calcHist()函数可以直接统计每个像素值的出现次数

下面是手工计算并绘制的代码,为了方便显示,我们归一化到直方图图像的高度即可

#include 
#include
#include
using namespace cv;
using namespace std;



int main()
{
	Mat srcMat = imread("D:/壁纸/3.jpg");
	float histgram[256][3];
	memset(histgram, 0, sizeof(histgram));
	float ma = 0;
	for (int i = 0; i < srcMat.rows; i++)
		for (int j = 0; j < srcMat.cols; j++)
		{
			int b = srcMat.at(i, j)[0];
			int g = srcMat.at(i, j)[1];
			int r = srcMat.at(i, j)[2];
			histgram[b][0]++;
			histgram[g][1]++;
			histgram[r][2]++;
			ma = ma < histgram[b][0] ? histgram[b][0] : ma;
			ma = ma < histgram[g][1] ? histgram[g][1] : ma;
			ma = ma < histgram[r][2] ? histgram[r][2] : ma;
		}
	int hist_h = 400;
	int hist_w = 512;
	int bin_w = hist_w / 256;
	Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
	float m = ma / hist_h;
	for (int i = 0; i < 256; i++)
	{
		histgram[i][0] /= m;
		histgram[i][1] /= m;
		histgram[i][2] /= m;
	}

	for (int i = 1; i < 256; i++)
	{
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(histgram[i-1][0])),
			Point(i*bin_w, hist_h - cvRound(histgram[i][0])), Scalar(255, 0, 0), 2, CV_AA);
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(histgram[i - 1][1])),
			Point(i*bin_w, hist_h - cvRound(histgram[i][1])), Scalar(0, 255, 0), 2, CV_AA);
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(histgram[i - 1][2])),
			Point(i*bin_w, hist_h - cvRound(histgram[i][2])), Scalar(0, 0, 255), 2, CV_AA);
	}
	imshow("circle", srcMat);
	imshow("histgram", histImage);
	waitKey(0);
	return 0;
}

Gamma校正

Gamma校正的原理是对输入图像灰度值进行的非线性操作,使输出图像灰度值与输入图像灰度值呈指数关系

这个指数即为Gamma.

经过Gamma校正后的输入和输出图像灰度值关系如图1所示:横坐标是输入灰度值,纵坐标是输出灰度值,蓝色曲线是gamma值小于1时的输入输出关系,红色曲线是gamma值大于1时的输入输出关系。可以观察到,当gamma值小于1时(蓝色曲线),图像的整体亮度值得到提升,同时低灰度处的对比度得到增加,更利于分辩低灰度值时的图像细节。

#include 
#include
#include
using namespace cv;
using namespace std;



void GammaCorrection(Mat& src, float Gamma)
{

	uchar lut[256];
	for (int i = 0; i < 256; i++)
		lut[i] = saturate_cast(pow((float)(i / 255.0), Gamma) * 255.0f);

	for (auto it = src.begin(); it != src.end(); it++)
		{
			(*it)[0] = lut[((*it)[0])];
			(*it)[1] = lut[((*it)[1])];
			(*it)[2] = lut[((*it)[2])];
		}

}

int main()
{
	Mat srcMat = imread("C:/Users/bluef/Pictures/5.jpg");
	imshow("before", srcMat);
	float Gamma = 1 / 2.2;
	GammaCorrection(srcMat, Gamma);
	imshow("after", srcMat);
	waitKey();
	return 0;
}

经过gamma校正后的图片

直方图均衡

直方图均衡化是通过拉伸像素强度的分布范围,使得在0~255灰阶上的分布更加均衡,提高了图像的对比度,达到改善图像主观视觉效果的目的。对比度较低的图像适合使用直方图均衡化方法来增强图像细节。

opencv有个函数可以对单通道图像进行直方图均衡,我们如果要对三通道的图像进行直方图均衡,可以将三通道分离,分别进行直方图均衡再合并(使用merge函数合并通道)

#include 
#include
#include
using namespace cv;
using namespace std;



int main()
{
	Mat srcMat = imread("C:/Users/bluef/Pictures/6.jpg");
	vector channels, channels2;
	split(srcMat, channels);
	channels2.resize(3);
	for (int i = 0; i < 3; i++)
		equalizeHist(channels[i], channels2[i]);
	Mat dstMat;
	merge(channels2, dstMat);
	imshow("src", srcMat);
	imshow("dst", dstMat);
	int histsize = 256;
	float range[] = { 0,256 };
	const float* histRanges = { range };
	Mat b_hist, g_hist, r_hist;
	Mat b_hist2, g_hist2, r_hist2;
	calcHist(&channels[0], 1, 0, Mat(), b_hist, 1, &histsize, &histRanges, true, false);
	calcHist(&channels[1], 1, 0, Mat(), g_hist, 1, &histsize, &histRanges, true, false);
	calcHist(&channels[2], 1, 0, Mat(), r_hist, 1, &histsize, &histRanges, true, false);
	calcHist(&channels2[0], 1, 0, Mat(), b_hist2, 1, &histsize, &histRanges, true, false);
	calcHist(&channels2[1], 1, 0, Mat(), g_hist2, 1, &histsize, &histRanges, true, false);
	calcHist(&channels2[2], 1, 0, Mat(), r_hist2, 1, &histsize, &histRanges, true, false);

	int hist_h = 400;
	int hist_w = 512;
	int bin_w = hist_w / histsize;
	Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
	Mat histImage2(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
	normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(b_hist2, b_hist2, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(g_hist2, g_hist2, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(r_hist2, r_hist2, 0, hist_h, NORM_MINMAX, -1, Mat());

	for (int i = 1; i < histsize; i++)
	{
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(b_hist.at(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(b_hist.at(i))), Scalar(255, 0, 0), 2, CV_AA);
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(g_hist.at(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(g_hist.at(i))), Scalar(0, 255, 0), 2, CV_AA);
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(r_hist.at(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(r_hist.at(i))), Scalar(0, 0, 255), 2, CV_AA);
	}
	for (int i = 1; i < histsize; i++)
	{
		line(histImage2, Point((i - 1) * bin_w, hist_h - cvRound(b_hist2.at(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(b_hist2.at(i))), Scalar(255, 0, 0), 2, CV_AA);
		line(histImage2, Point((i - 1) * bin_w, hist_h - cvRound(g_hist2.at(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(g_hist2.at(i))), Scalar(0, 255, 0), 2, CV_AA);
		line(histImage2, Point((i - 1) * bin_w, hist_h - cvRound(r_hist2.at(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(r_hist2.at(i))), Scalar(0, 0, 255), 2, CV_AA);
	}
	imshow("srch", histImage);
	imshow("dsth", histImage2);
	waitKey(0);
	return 0;
}

可以很明显发现图像经过直方图均衡后,效果增强了很多,直方图从集中在中后部变成了分散开来