基于QT,C++和opencv 的人脸识别项目(二)

目录

    • 引言
    • opencv的介绍
        • 什么是opencv?
        • opencv的主要模块和contrib模块
        • linux下要求的packages
        • opencv的基本操作
          • 1.图像
          • 2.视频
          • 3.绘图功能
          • 5.图像的基本操作
          • 6.图像上的算术运算
          • 7.性能衡量和提升技术
          • 8.改变颜色空间
          • 9.图像的几何变换
          • 10.离散傅里叶变换DFT
        • 模型

引言

工欲善其事,必先利其器。我们先了解可能用到的相关知识。主要包含:opencv,相关模型。

这是本项目的第二篇文章。
第一篇文章,主要介绍项目的任务和实验环境,点击阅读
第三篇文章,主要介绍人脸检测haar+adaboost的原理,点击阅读。
第四篇文章,主要介绍PCA降维和人脸识别的原理,点击阅读。
第五篇文章,主要给出相关的代码,点击阅读。

opencv的介绍

本实验主要是基于opencv实现的,了解opencv也就是重中之重了。学习opencv最好的地方当然是opencv官网,当然囿于英语阅读的速度和方便性,我把有关项目的重要信息摘要给大家。

什么是opencv?

OpenCV是一个计算机视觉库,由C和C++编写,包含着大量的图像处理函数,其拥有500多个函数,支持跨平台使用,同时也有免费、速度快和使用方便的优点。
下图是opencv的主体架构图:
基于QT,C++和opencv 的人脸识别项目(二)_第1张图片

OpenCV的具体应用:扫描对象的对齐、医学图像去噪、图像中的物体分析、安全和入侵检测系统、自动监视、产品质量检测系统、摄像机标定、军事应用、无人飞行器、无人汽车和无人水下机器人。

opencv的主要模块和contrib模块

基于QT,C++和opencv 的人脸识别项目(二)_第2张图片基于QT,C++和opencv 的人脸识别项目(二)_第3张图片

linux下要求的packages

  • GCC(GCC是linux最常用的C/C++编译器,通常以gcc命令的形式在终端shell中使用),GCC教程
  • Cmake(对于软件跨平台,存在一个问题,就是不同的make工具有着不同的规范和标准,所以不同的图软件就得编写不同的makefile,cmake针对上面问题设计如下:它首先允许开发者编写一种平台无关的
  • CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如
  • Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。)
  • Git(Git是目前世界上最先进的分布式版本控制系统)Git教程
  • GTK(是一套跨多种平台的图形工具包)
  • python
  • ffmpeg(一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序)

opencv的基本操作

1.矩阵的使用与操作:创建删除,初始化,访问矩阵元素,矩阵或其元素的加减乘除,特征值的求解等等
2.GUI命令:创建窗口,显示图片,等待按键等等
3.图像的使用与操作:读写图像,图像转换,图像处理函数等等
4.视频的使用与操作:打开视频/摄像头,捕捉帧,保存帧
具体如下:
改变颜色空间、图像几何变换、图像阈值、图像平滑、形态转换、canny边缘检测、轮廓检测、直方图、傅里叶变换、模板匹配、图像分割、分水岭算法、特征检测、特征匹配、相机校准、姿态估计等等操作

1.图像
  • 读取图像

       cv::imread()
       //其中第一个参数是图像的路径,第二个参数制定了读取的方式。
       IMREAD_COLOR: 加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。
       IMREAD_GRAYSCALE:以灰度模式加载图像
       IMREAD_UNCHANGED:加载图像,包括alpha通道
       除了这三个标志,你可以分别简单地传递整数1、0或-1。
       //example
       cv::imread("../home/picture/1.png",cv::IMREAD_GRAYSCALE)
    
  • 显示图像
    cv::imshow()。第一个参数是窗口名称,它是一个字符串。第二个参数是我们的对象。你可以根据需要创建任意多个窗口,但可以使用不同的窗口名称。
    cv::waitKey()。是一个键盘绑定函数。其参数是以毫秒为单位的时间。如果0被传递,它将无限期地等待一次敲击键。它也可以设置为检测特定的按键,例如,如果按下键 a 等。
    cv::destroyAllWindows()只会破坏我们创建的所有窗口。如果要销毁任何特定的窗口,请使用函数 cv::destroyWindow()在其中传递确切的窗口名称作为参数。
    如果想提前创建一个窗口,则可以使用cv::namedWindow()函数。这个窗口可以自己指定大小。可以选择的参数为:cv::WINDOW_AUTOSIZE cv::WINDOW_NORMAL,第二个参数可以调整窗口大小。

       //example:
      	  cv::Mat a = cv::imread("1.jpg");
      	  cv::imshow("windowname",a);
      	  cv::waitKey(0);
      	  cv::destroyWindow();
      	  cv::namedWIndow("windowname",cv::WINDOW_NORMAL)
      	  cv::imshow("windowname",a);
    
  • 写入图像
    使用cv::imwrite()保存图像。第一个参数是文件名,第二个参数是保存的图像。

    //example
    cv::imwrite(“picture”,a);

2.视频
  • 从摄像机捕捉实时画面。
    要捕获视频,首先创建一个VideoCapture对象,他的参数可以使设备的索引或视频文件的名称。比如WINDOWS的摄像头的索引就是0,在LINUX下可能是0或者-1。然后通过传递1来选择第二个相机,依次类推。

       //example:
       cv:;VideoCapture cap(0);
       if(!cap.isOpend())
       	std::cout<<"error'<> frame;
       		cv::imshow("frame",frame);
       		cv::waitKey(10);
       	}
       cv::cap.release();
       cv::destroyAllWindows();
    
  • 从文件中播放视频
    和上面的步骤一样,但是需要把cap后面的参数改为视频文件的路径。

3.绘图功能
  • 划线
    cv::line()。要绘制一条线,需要传递线的开始和结束坐标。

       cv::line(img,(0,0),(511,511),(255,0,0),5);
       //四个参数分别为:图像,开始点,结束点,线的宽度
    
  • 画矩形
    绘制矩形,需要矩形的左上角和右下角。

      cv::rectangle(img,(384,0),(510,128),(0,250,0),3);
      //四个参数为图像,左上角坐标,右下角坐标,颜色,宽度
    
  • 画圆圈
    要绘制一个圆,需要其中心坐标和半径。

      cv::circle(img,(447,63),63,(0,,0,255),-1)
      //参数分别为:图像,圆心点坐标,半径,颜色,宽度
    
  • 添加文本

      //example
      font = cv::FONT_HERSHEY_SIMPLEX;
      cv::putTect(img,"words",(10,500),font,4,(255,255,255),2,cv::LINE_AA);
    

    4.鼠标事件

      cv::setMouseCallback()
    
5.图像的基本操作
  • 访问和修改像素值
    可通过行和列的坐标来访问像素值。对于BGR图像,他返回一个由蓝色、绿色和红色组成的数组。对于灰度图像,返回相应的灰度。

       //example
       px = img[100,100];   //访问
       print(px);
       img[100,100]=[255,255,255];   //修改
       print(img[100,100]);
       //另一种方法
       img.item(10,10,2);  //访问
       img.item((10,10,2),100);   //修改
       img.item(10,10,2); 
    
  • 访问图像属性
    图像的形状可通过img.shape访问。它返回行,列和通道数的元组(如果图像是彩色的)。如果图像是灰度的,则返回的元组仅包含行数和列数,因此这是检查加载的图像是灰度还是彩色的好方法。

    print(img.shape);  //图像的形状
    print(img.size);   //像素总数
    print(img,dtype);    //获得图像数据类型
    
  • 访问感兴趣区域ROI

    ball = img[280:340, 330:390];
    img[273:333,100:160]  = 160;
    
6.图像上的算术运算
  • 图像加法和融合

     cv::add(),两个图像相加。
     cv::addWeighted(),两个图像融合
    
  • 按位运算
    这包括按位AND、OR、NOT 和 XOR 操作。它们在提取图像的任何部分、定义和处理非矩形 ROI 等方面非常有用。

7.性能衡量和提升技术
  • 使用opencv衡量性能
    cv::getTickCount函数返回从参考事件到到用此函数那一刻之间的时钟周期数。
    cv::getTickFrequency函数返回时钟周期的频率或每秒的时钟周期数。
8.改变颜色空间
  • opencv中有超过150种颜色空间转换方法。

    cvtColor(input_image,flag),其中flag决定转换的类型。
    COLOR_BGR2GRAY,COLOR_BGR2HSV
    
9.图像的几何变换
  • 缩放
    cv::resize()

     //example
     img = cv::imread('messi5.jpg')
     res = cv::resize(img,None,fx=2, fy=2, interpolation = INTER_CUBIC)
    
  • 平移
    如果知道在(x,y)方向上平移,则将其设为(tx,ty),创建转换矩阵M
    在这里插入图片描述
    调用时:

      cv::warpAffine(img,M,(cols,rows));   //最后一个参数是输出图片的大小
    
  • 旋转
    在这里插入图片描述
    其中:在这里插入图片描述

    cv::getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1);
    dst = cv.warpAffine(img,M,(cols,rows))
    
10.离散傅里叶变换DFT

对一张图像使用傅立叶变换就是将它分解成正弦和余弦两部分。也就是将图像从空间域(spatial domain)转换到频域(frequency domain)。这一转换的理论基础来自于以下事实:任一函数都可以表示成无数个正弦和余弦函数的和的形式。傅立叶变换就是一个用来将函数分解的工具。 2维图像的傅立叶变换可以用以下数学公式表达:
傅里叶变换
式中 f 是空间域(spatial domain)值, F 则是频域(frequency domain)值。 转换之后的频域值是复数, 因此,显示傅立叶变换之后的结果需要使用实数图像(real image) 加虚数图像(complex image), 或者幅度图像(magitude image)加相位图像(phase image)。 在实际的图像处理过程中,仅仅使用了幅度图像,因为幅度图像包含了原图像的几乎所有我们需要的几何信息。
步骤如下:
1.将图像延扩到最佳尺寸:离散傅立叶变换的运行速度与图片的尺寸息息相关。当图像的尺寸是2, 3,5的整数倍时,计算速度最快。 因此,为了达到快速计算的目的,经常通过添凑新的边缘像素的方法获取最佳图像尺寸。函数 getOptimalDFTSize() 返回最佳尺寸,而函数 copyMakeBorder() 填充边缘像素:

	Mat padded;                            //将输入图像延扩到最佳的尺寸
	int m = getOptimalDFTSize( I.rows );
	int n = getOptimalDFTSize( I.cols ); // 在边缘添加0
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

2.为傅立叶变换的结果(实部和虚部)分配存储空间. 傅立叶变换的结果是复数,这就是说对于每个原图像值,结果是两个图像值。 此外,频域值范围远远超过空间值范围, 因此至少要将频域储存在 float 格式中。 结果我们将输入图像转换成浮点类型,并多加一个额外通道来储存复数部分:

	Mat planes[] = {Mat_(padded), Mat::zeros(padded.size(), CV_32F)};
	Mat complexI;
	merge(planes, 2, complexI);         // 为延扩后的图像增添一个初始化为0的通道

3.进行离散傅立叶变换. 支持图像原地计算 (输入输出为同一图像):

	dft(complexI, complexI);            // 变换结果很好的保存在原始矩阵中

4.将复数转换为幅度.复数包含实数部分(Re)和复数部分 (imaginary - Im)。 离散傅立叶变换的结果是复数,对应的幅度可以表示为:在这里插入图片描述
转化为OpenCV代码:

	split(complexI, planes);                   // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
	magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
	Mat magI = planes[0];

5.对数尺度(logarithmic scale)缩放. 傅立叶变换的幅度值范围大到不适合在屏幕上显示。高值在屏幕上显示为白点,而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,我们可以用对数尺度来替换线性尺度:
在这里插入图片描述
转化为OpenCV代码:

	magI += Scalar::all(1);                    // 转换到对数尺度
	log(magI, magI);

6.剪切和重分布幅度图象限. 还记得我们在第一步时延扩了图像吗? 那现在是时候将新添加的像素剔除了。为了方便显示,我们也可以重新分布幅度图象限位置(注:将第五步得到的幅度图从中间划开得到四张1/4子图像,将每张子图像看成幅度图的一个象限,重新分布即将四个角点重叠到图片中心)。 这样的话原点(0,0)就位移到图像中心。

	magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
	int cx = magI.cols/2;
	int cy = magI.rows/2;
	
	Mat q0(magI, Rect(0, 0, cx, cy));   // Top-Left - 为每一个象限创建ROI
	Mat q1(magI, Rect(cx, 0, cx, cy));  // Top-Right
	Mat q2(magI, Rect(0, cy, cx, cy));  // Bottom-Left
	Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
	
	Mat tmp;                           // 交换象限 (Top-Left with Bottom-Right)
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);
	
	q1.copyTo(tmp);                    // 交换象限 (Top-Right with Bottom-Left)
	q2.copyTo(q1);
	tmp.copyTo(q2);

7.归一化. 这一步的目的仍然是为了显示。 现在我们有了重分布后的幅度图,但是幅度值仍然超过可显示范围[0,1] 。我们使用 normalize() 函数将幅度归一化到可显示范围。

	normalize(magI, magI, 0, 1, CV_MINMAX); // 将float类型的矩阵转换到可显示图像范围
	                                        // (float [0, 1]).

离散傅立叶变换的一个应用是决定图片中物体的几何方向.比如,在文字识别中首先要搞清楚文字是不是水平排列的? 看一些文字,你就会注意到文本行一般是水平的而字母则有些垂直分布。文本段的这两个主要方向也是可以从傅立叶变换之后的图像看出来。我们使用这个 水平文本图像 以及 旋转文本图像 来展示离散傅立叶变换的结果 。

模型

有初学者可能会由疑问,什么是模型,为什么要用模型和怎么训练模型呢?
模型:简单来说就是函数,对于使用者来说,就是一个黑盒子,我们将需要操作的数据作为输入,经过黑盒子(模型)处理,得到输出。例如在人脸检测中我们在这里输入的一张带有人的图片,希望得到的是人脸的位置或者说是人脸的图片。
在这里中间进行人脸检测的,就是我们所说的模型。
对于大量的数据,有了封装好的模型,我们可以不动脑筋的将图片输入进去,得到想要的图片。所以,训练一个好的模型,对于项目至关重要。
怎么训练模型呢,我们一般由监督学习和非监督学习两种方法。在监督学习中,我们给出两组数据,一般是training data和testing data,即训练数据和测试数据,两个数据集的比例一般是9:1。我们在训练集上训练我们的模型,在测试集上获得我准确率。而何为监督呢,就是我们在数据集中会带有label,即我们会在数据中,给出它应该有的的正确输出。在有些监督学习中,也会有一个验证集。三者的比例通常是8:1:1。通过大量的数据,我们调整模型(函数)的参数,使它的预测的错误率达到最小。

在本次项目中,我们所用到的两个关键点是:
Haar+Adaboost级联器 Haar特征和Adaboost迭代算法
Eigenface 特征脸算法

这两个模型的原理介绍,移步第三篇文章:基于QT,C++和opencv 的人脸识别项目(三)

你可能感兴趣的:(C++,qt,opencv)