使用OpenCV的计算机视觉
在本章中,我们将学习如何使用计算机视觉算法来使用ofxOpenCv插件和OpenCV库进行高级视频分析和处理。您将学习如何使用插件类的图像和执行过滤,图像的几何变换,并在图像中找到对象的轮廓。最后,我们将在一个例子中考虑如何使用本地OpenCV函数,并学习如何使用光流。我们将讨论以下议题:
使用ofxOpenCv
动作感应
图像过滤
图像的几何变换
搜索图像中的对象
光流
视频变形
理解计算机视觉和Opencv
计算机视觉是介于数学和计算机科学之间的一个广泛的科学领域。它的主要目标是建立自动的方法来理解图像的内容。这个目标很难实现;然而,我们已经有了很多伟大的算法,包括图像增强和校正、目标检测、跟踪和识别、立体视觉和自动机器学习。
你会发现,许多算法,如图像滤波和目标跟踪是常见的计算机视觉和视频处理。原因是在处理图像时,一些基本的步骤是很常见的。区别在于目标。
计算机视觉的目标是对用于控制机器的摄像机的图像进行自动分析,例如,机器人或者交互式设备。视频处理的目标是为观众创造视频和视频效果。
如果要使用现有的计算机视觉方法,一个不错的选择是开放式计算机视觉(OpenCV)库。它包含了数百种图像处理和分析的经典算法和最新算法。目前,该图书馆正在通过KhronosGroup(Khronos.org)进入标准化阶段,很快就会像OpenGL和OpenCL一样成为标准。
注意:虽然OpenCV使用立体视觉并具有转换三维点云的功能,但它主要致力于处理从摄像机获得的二维图像。因此,对于处理和分析从深度相机获得的三维点云,您应该使用自己的算法。此外,您还可以使用PCL库,这是一个用于处理3D点云的特殊库。但是,请注意,这超出了本书的范围。
Opencv具有独特的CPU和GPU功能。我们只考虑CPU函数,因为这些函数比较容易学习。永远记住OpenCV函数是高度优化的,所以它们总是比你自己的逐像素算法运行得更快。然而,CPU的运算速度不够快,无法实时处理像全高清帧这样的大图像。为了达到这个目的,你需要使用GPU。如果你只需要一些视频效果的图像处理,你可以使用着色器技术而不用OpenCV,因为使用着色器通常更简单,更通用。然而,当真正需要计算机视觉的东西,然后使用OpenCV的GPU功能。
开始将OpenCV链接到openFrameworks项目的方法是使用ofxOpenCv插件。它还增加了一些类,简化了图像处理和跟踪的常见任务。本章的大部分内容将专门讨论ofxOpenCv,只有最后一节使用OpenCV函数将专门讨论直接使用OpenCV函数。
注意:正常情况下,ofxOpenCv插件是相当新鲜和稳定的,但它不是OpenCV的最新版本。如果出于某种原因你需要最新版本的OpenCV,从它的网站下载并链接到你的项目。注意,这个过程需要一些使用库的经验。
使用ofxOpenCv
在项目中使用ofxOpenCv插件最直接的方法是基于ofxOpenCv的使用实例启动一个新项目。为此,将examples/addons/opencvexample文件夹复制到项目文件夹中(例如,apps/myapps)并将其重命名(例如,重命名为myCompVision)。
第二种方法是使用openFrameworks中包含的ProjectGenerator向导生成一个新项目。它允许您选择要链接到项目的插件,然后创建一个包含所需插件的新空项目。在我们的例子中,你需要包括ofxOpenCv。详细信息请参阅附录a,使用插件。
当项目已经被复制或生成时,您需要在#include"ofMain.h"行之后将插件头包含到testApp.h文件中:
#include "ofMain.h"
#include "ofxOpenCv.h"
ofxOpenCv插件是类的集合。类的名称以ofxCV开头。有两组类:图像类和算法类。
Image类包含不同类型的图像,另外还有一组Opencv执行的图像处理功能:
1.OfxCvColorImage类表示三通道(红色、绿色和蓝色)彩色图像,其颜色组件类型为无符号char。这些图像是从照相机中获得的。
2.OfxCvGrayscaleImage类表示灰度级的单通道图像,其像素值类型为无符号char。这些图像用于内部处理,如阈值和轮廓发现,并作为一个类工作与二进制图像只包含两个像素值(0和255)。
3.OfxCvFloatImage类表示灰度图像,单通道图像,类型为float。它用于需要精确计算的场合;例如,用于平滑和傅里叶变换的特殊滤波器。
4.OfxCvShortImage类表示灰度级的单通道图像,其像素值类型为无符号short int。像素值的范围是从0到65,535。这些图像是从深度相机中获得的,其中像素值以毫米为单位表示相应的距离。
5.OfxCvImage类是前面所有映像类的基类。它包含大多数图像处理函数的声明,因此您可以浏览它们。注意,不应该直接声明这个类的对象,因为它有许多抽象函数,即声明但没有定义的函数。因此,使用类ofxCvImage的对象调用这个类会导致执行错误。
注意:目前,还没有实现三通道浮点类型和无符号短类型图像,也没有四通道(红、绿、蓝和alpha)图像类型。
算法类实现了两个计算机视觉算法,它们是:
1.OfxCvContourFinder类查找连接的边界轮廓,输入二进制图像中的白色区域。这样的类可用于搜索对象。请参阅OfxCvContourFinder使用类中的查找轮廓部分的详细信息。
2.OfxCvHaarFinder类实现了Viola-Jones在图像上搜索对象的方法,这种方法使用哈尔特征和一种叫Boosting的机器学习方法。这种方法特别适用于在图像上搜索不同大小的正面人脸。请参阅openFrameworks示例,在examples/addons/opencvHaarFinderExample中搜索面孔。
现在我们将更详细地了解图像类和图像处理。
使用ofxCv图像
计算机视觉的主要对象是图像。因此,在深入研究图像处理和分析之前,我们应该学习如何自由处理与opencv相关的图像。我们将讨论ofxCvColorImage、OfxCvGrayscaleImage、OfxCvFloatImage和OfxCvShortImage的图像类。这些类的名称有前缀ofxCv,因此我们将它们称为ofxCv图像。准确地说,我们假设我们有这些图像对象:
ofxCvColorImage image, image2; //Color images
ofxCvGrayscaleImage grayImage, grayImage2; //Grayscale images
ofxCvFloatImage floatImage; //Float-type image
ofxCvShortImage shortImage; //Image with "unsigned short" pixels
它可以方便地将函数和操作分成若干组。
图像初始化
在第一次使用映像之前,始终需要对其进行初始化一些用于初始化图像的函数:
1.image.allocate(w,h)函数使用宽度w和高度h像素初始化图像;例如,image.allocate(320,240)创建大小为320*240像素的图像。
注意:像素值在初始化后可以是非零值,因此如果需要设置它的初始值,请使用set(value)函数(请参阅本节末尾给出的描述)。
2.=操作符复制相等或不同类型的图像并执行必要的像素转换;例如:
image2 = image;将图像复制到image2。请注意,没有必要使用image2初始化image2.allocate(w, h),因为它是自动初始化的。
grayImage = image; 将彩色图像转换为浮动灰度图像;
floatImage = grayScale; 将灰度图像转换为浮动图像
这里重要的是初始化。如果目标映像未初始化,则操作员将自动执行所需的初始化。但是,如果映像已初始化,则它的大小必须等于源映像的大小。在相反的情况下,您应该使用clear()函数清除图像,或者使用resize()函数设置必备大小。
提醒:在图像类型转换过程中,像素值的范围随图像类的范围相应地发生变化。OfxCvColorImage和ofxCvGrayscaleImage的范围是0到255,而ofxCvFloatImage的范围是分段[0,1],ofxCvShortImage的范围是0到65535。例如,在floatImage=grayImage操作期间,floatImage的指定像素值等于grayImage的像素值乘以1.0/255.0。因此,floatImage的像素值将位于[0,1]。类似地,在grayImage=shortImage操作期间,grayImage的指定像素值等于shortImage图像的像素值乘以255.0/65535.0。可以使用setNativeScale(vMin,vMax)函数将ofxCvFloatImage的范围更改为任何值;例如,floatimage.setNativeScale(0.0,255.0)将范围设置为[0,255]。
3.setFromPixels(data,w,h)函数将图像维度设置为w*h像素,并将其像素值设置为来自无符号字符数组数据的值。注意,数据数组的大小应该等于ofxCvColorImage的w*h*3和其他类型的w*h。还有一种替代形式,setFromPixels(pixels),像素具有像素类型;例如,如果您使用videograbber抓取器的对象从摄像机获取视频帧(参见第五章视频处理中的摄像机实时视频部分),您可以以下列方式初始化图像:
image.setFromPixels( grabber.getPixelsRef() );
4.对于浮动图像,有一个重载函数用于从一个浮动数组中设置像素,该数组称为setFromPixels(datfloat,w,h),其中datfloat是浮动数组。
5.为了检查是否分配了图像,使用bAllocated成员,它的类型bool如下面的代码所示:
if ( image.bAllocated ) {
//...
}
有两个函数接近初始化阶段:
6.Set(value)函数将所有图像像素设置为该值。对于彩色图像,还有一个函数集set(valueRed、valueGreen、valueBlue),用于将红色、绿色和蓝色组件的每个像素设置为分别为valueRed、valueGreen和valueBlue。
7.Clear()函数清除为图像分配的所有内存。通常,您不需要使用这个函数,因为它是由图像的析构函数自动调用的。
带图像的代数运算
代数运算对图像的每个像素应用诸如加法和减法的数学运算。下面是一些代数函数:
1.具有ofxCV图像类型操作数的+=、-=和*=操作适用于大小和类型相等的图像。这些操作对两个图像的相应像素执行相应的操作;例如,image+=image2向image添加image2。
提醒:当前,*=操作将ofxCvFloatImage之外的所有图像的操作数除以255.0。
2.使用浮点操作数参数值的+=、-=、*=和/=操作分别对图像中的所有像素值执行加、减、乘和除操作,并在值变量中指定值;例如,image+=1将1加到图像的像素值中。*=和/=操作目前只能用于类的浮点图像OfxCvFloatImage.
注意:目前,*操作将负像素值截断为零。因此,在中间操作期间需要处理负像素值时,不要使用此操作。因此,不要调用floatImage*=value,而是调用multiplyByScalar(floatImage,value)函数,该函数的代码如下:
void multiplyByScalar( ofxCvFloatImage &floatImage, float value ){
int w = floatImage.width;
int h = floatImage.height;
float *floatPixels =
floatImage.getPixelsAsFloats();
for (int y=0; y<h; y++) {
for (int x=0; x<w; x++) {
//Change pixels values
floatPixels[ x + w * y ] *= value;
}
}
//Notify openFrameworks that
//the image was changed
floatImage.flagImageChanged();
}
3.函数grayImage.absDiff(grayImage2)计算相应像素之间的绝对差值,并将结果写入grayImage。有一个重载的grayImage.absDiff(grayImageA,grayImageB)函数,它将grayImageA和grayImageB之间的绝对差值的结果放到grayImage中。虽然这在形式上不是一个代数运算,但显然与它们有关。这个函数对于标记两幅图像不同的区域很有用。注意,absDiff函数目前只适用于灰度图像。
除了ofxCvFloatImage之外的所有图像类型都具有有限的像素值范围(请参阅前面的操作员信息框中关于范围的讨论)。如果任何操作的结果超出范围,则应用饱和算法,即将值截断为范围。例如,对于ofxCvGrayscaleImage类型的图像,值300和-10将分别被截断为255和0。因此,如果您使用图像执行高级数学计算,最好先将输入图像转换为ofxCvFloatImage,然后执行计算,最后将最终结果转换为所需类型。
绘图功能
绘图函数类似于openFrameworks图像的ofImage类的对应函数,在第4章图像和纹理中讨论过。以下是一些绘图函数:
1.Draw(x,y,w,h)函数将图像绘制到屏幕。注意,对ofxCvFloatImage和ofxCvShortImage类的图像,像素值从相应的范围[0,1]和0到65,535映射到0到255的范围,从而实现屏幕输出。浮动图像的范围可以使用setNativeScale(vMin,vMax)函数来更改。有多个重载版本:draw(x,y)、draw(point)和draw(rect),它们的类型是point和rect,类型是rectangle。
2.SetAnchorPercent(xPct,yPct)、setAnchorPoint(x,y)和resetAnchor()函数允许我们控制输出映像的原点,就像在ofImage中一样。
3.使用bool类型的setUseTexture(use)函数启用或禁用图像的纹理。此纹理仅在绘制图像之前自