OpenCV是一个基于C/C++语言的开源图像处理函数库,其特点有:
1.
代码都是经过优化,可以用于实时处理图像;
2.
具有良好的可移植性;
3.
可以进行图像/视频的载入、保存和采集的常规操作;
4.
具有低级和高级的API;
5.
提供了面向Intel
IPP高效多媒体函数的接口,可以针对使用的IntelCPU优化代码,提高程序新跟那个(OpenCV自从2.0版本以后已经不需IPP,所以不再提供相关的接口)
【OpenCV功能】
1.
图像数据操作(内存分配与释放,图像复制,设定和转换);
2.
图像/视频的输入和输出(支持文件或者摄像头的输入,图像/视频文件的输出;
3.
矩阵/向量的数据操作以及线性代数运算(矩阵乘积、矩阵方程求解、特征值、
奇异值分解);
4.
支持多种动态数据结构(链表、队列、数据集、树、图);
5.
基本图像处理(去噪、边缘检测、角点检测、采样与插值、色彩变换、形态学处理、直方图、图像金字塔结构);
6.
结构分析(连通域/分支、轮廓处理、距离转换、图像矩、模板匹配、霍夫变幻、多项式逼近、曲线拟合、椭圆拟合、狄劳尼三角化;
7.
摄像头定标(寻找和跟踪定标模式、参数定标、基本矩阵估计、单位矩阵估计、立体视觉匹配);
8.
运动分析(光流、动作分割、目标跟踪);
9.
目标识别(特征方法、HMM模型);
10.
基本的GUI(显示图像/视频、键盘/鼠标操作、滑动条);
11.
图像标注(直线、曲线、多边形、文本标注)。
【OpenCV基本模块】
cv——核心函数库
cvaux——辅助函数库
cxcore——数据结构与线性代数库
highgui——GUI函数库
ml——机器学习函数库
【OpenCV命名规则】
A.
函数名
cvActionTargetMod(…)
Action=核心功能(core functionality)(eg:set,create)
Target=目标图像区域(target image area)
(eg:contour,polygon)
Mod=(可选的)调整语(optional modifiers)(eg:argument type)
B.
矩阵数据类型
CV_(S|U|F)
S=符号整形
U=无符号整形
F=浮点型
(eg:CV_8UC1是指一个8位无符号整形单通道矩阵,
CV_32FC2是指一个32位浮点型双通道矩阵)
C.
图像数据类型
IPL_DEPTH(S|U|F)
Eg:
IPL_DEPTH_8U图像像素数据是8位无符号整形。
IPL_DEPTH_32P图像像素数据是32位浮点型。
【头文件包含】
#include——核心函数库
#include——辅助函数库
#include——机器学习库
#include——GUI函数库
#include//一般不需要,因为cv.h已经包含该头文件
数据结构与线性代数库
【OpenCV中的基本数据结构】
1.
图像数据结构
A.
IPL图像
IPLImage
Int nChannels
颜色通道的数目(1,2,3,4)
Int depth
像素的位深
IPL_DEPTH_8U
IPL_DEPTH_16S
IPL_DEPTH_32F
IPL_DEPTH_64F
Int width
图像宽度(像素为单位)
Int height
图像高度
Char * imageData
图像数据指针
彩色图像按照BGR的顺序存储数据
Int dataOrder
0——将像素点不同的通道的值交错排在一起,形成单一的像素平面
1——把所有的像素同通道值排在一起,形成若干个通道平面,再把平面排列起来
Int origin
0 –像素原点=左上角
1 –像素原点为左下角(windows bitmaps
Style)
Int widthStep
相邻行的同列点之间的字节数
Int imageSize
图像的大小(字节为单位)=height*widthStep
Struct _IplROI *roi
图像的感兴区域(ROI),ROI非空的会后对图像的处理仅限于ROI区域
Char *imageDataOrigin
图像数据未对齐时候的数据原点指针
(需要正确地重新分配图像内存)
Int align
图像数据的行对齐 :4 or 8 byte
alignment
Char colorModel[4]
颜色模型(OpenCV中没有此项)
2.
矩阵:
A.2D矩阵
CvMat(2D矩阵)
int type
元素类型
int step
整行长度字节数
Int rows,cols
行、列数
int height,width
矩阵高度、宽度、与rows,cols对应
Union data
Uchar *ptr
指向unsigned
char矩阵的指针
Short *s
指向short矩阵的指针
Int * i
指向整形矩阵的指针
Float *fl
指向浮点型矩阵的指针
Double *db
指向双精度浮点型矩阵的指针
B.
N维矩阵
CvMatND(N-维矩阵)
Int type
元素类型(uchar,short,int,float,double)
Int dims
矩阵维数
Union data
Uchar *Ptr
Short *s
Int *
I;
Float *fl;
Double *db
Struct dim[]
各维信息
Size 元素数目
Step 元素间距 字节为单位
C.
CvSparseMat
//N-维稀疏矩阵
D.一般矩阵
CvArr* //仅仅作为函数定义的参数使用
//表明函数可以接受不同类型的矩阵作为参数
//矩阵的类型通过矩阵头部的前4个字节信息来确定
E.标量
CvScalar double val[4];
//4D向量
初始化:
CvScalar s=cvScalar(double val0,double val1=0, double val2=0,
double val3=0);
CvScalar s=cvScalar(20.0);
s.val[0]=20.0
3.
点
CvPoint p=cvPoint(int x,int
y); //整形二维点
CvPoint 2D32f
p=cvPoint2d32f(float x,float y);//浮点型二维点
Cvpoint3D32f
p=cvPoint3D32f(float x,float y,float z);//浮点型三维点
4.
矩形框大小
(以像素为精度)
cvSize=
cvSize(int width,int height)
CvSize2D32f r=cvSize2D32f(float
width,float height);
5.
矩形框的偏置和大小:
CvRect r=cvRect(int x,
int y, int width , int
height)
【OpenCV的图像读写】
1.
从文件中读入图像:
OpenCV默认将读入的图像强制转换为一副三通道彩色图像:
IplImage
* img=0;
Img=cvLoadImage(fileName);
If(!img)
printf(“could
not load image file :%
s\n”,filename);
OpenCV支持图像格式有:BMP、DIB、JPEG、JPE、PNG、PBM、PGM、PPM、SR、RAS、TIFF、TIF
可以按照如下的方式修改读入的方式:
img=cvLoadImage(filename,flag)
flag: >0 将读入的图像强制转换为一副三通道彩色图像
=0 将读入的图像强制转换为一副单通道灰度图像
<0 将读入的图像通道数与所读入的文件相同
2.
保存图像
If(!cvSaveImage(outFileName,img)) printf(“could not save
:%s\n”,outFileName)
保存的图像的格式由outFileName中的扩展名确定
【OpenCV访问图像像素】
假设现在需要访问第k通道、第i行,第j列的像素,可以有如下的间接和直接访问两种方式:
1.
间接访问(单通道字节型图像)
IplImage *img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
CvScalar s;
S=cvGet2D(img,i,j);//注意本函数中坐标参数的顺序与其它的openCV函数坐标参数顺序恰好相反,本函数中的i代表height,而j代表width
Printf(“intensity=%f\n”,s.val[0]);
s.val[0]=111;
cvSet2D(img,I,j,s); //设置img(J,i)位置的像素值为s
间接访问(多通道字节型/浮点型图像)
IplImage *
img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3)
cvScalar s;
s=cvGet2D(img,I,j) //得到(j,i)位置的像素值
printf(“B=%f,G=%f,
R=%f\n”,s.val[0],s.val[1],s.val[2]);
S.val[0]=111;
S.val[1]=111;
S.val[2]=111;
cvSet2D(img,I,j,s)//设置img的(j,i)位置的像素的值为s
2.
直接访问(效率很高,但是很容易出错)
单通道字节型图像:
IplImage
*img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
((uchar*
)(img->imageData+i*img->widthstep))[j]=111;
多通道字节型图像:
IplImage *
img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
((uchar*)
(img->imageData+i*img->widthStep))[j*img->nChannels+0]=111;
((ucahr
*)(img->imageData+i*img->widthStep))[j*img->nChannels+1]=112;
((uchar
*)(img->imageData+i*img->widthStep))[j*img->nChannels+2]=113;
多通道浮点型图像:
IplImage*
img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
((float *)(img->imageData +
i*img->widthStep))[j*img->nChannels + 0]=111; //
B
((float *)(img->imageData +
i*img->widthStep))[j*img->nChannels + 1]=112; //
G
((float *)(img->imageData +
i*img->widthStep))[j*img->nChannels + 2]=113; //
R
3.
基于指针的直接访问
单通道字节型图像:
IplImage
*img=cvCreateImage(cvSIze(640,480),IPL_DEPTH_8U,1)
Int
height =img->height;
Int
width =img->width;
Int
Step =img->widthStep;
Uchar
*data =(uchar
*)img->imageData
Data[i*setp+j]=111;
多通道浮点型图像(假设图像数据采用4字节(32位)行对齐方式)
IplImage
*img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3)
int height =img->height;
int width =img->width;
int step =img->widthStep;
int channels =img->nChanns
float
*data =(float *)img->imageData
data[i*step+j*Channels+k]=111
4.
基于C++
wrapper的直接访问(更加简单高效)
首先定义一个C++ wrapper
‘Image’,然后基于Image定义不同类型的图像:
Template class Image
{
Private:
IplImage *imgp;
Public
:
Image(IplImage *img=0){imgp=img}
~Image(){imgp=0;}
Void
operator=(IplImage
*img){imgp=img;}
Inline
T * operator[]=(const
int rowIndx){
Return ((T
*)(imgp->imageData+rowIndx*imgp->widthStep));}
};
Typedef
struct{
Unsigned char b,g,r;
}RgbPixel
Typedef struct{
Float b,g,r;
}RgbPixelFloat;
typedef
Image RgbImage;
typedef Image RgbImageFloat;
typedef Image BwImage;
typedef image BwImageFloat;
对于单通道字节型图像:
IplImage *img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1)
BwImage imgA(img);
imgA[i][j]=111;
对于多通道字节型图像:
IplImage *
img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
RgbImage imgA(img);
imgA[i][j].b=111;
imgA[i][j].g=111;
imgA[i][j].r=111;
对于多通道浮点型图像
IplImage
*img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3)
RgbImageFloat
imgA(img);
imgA[i][j].b=111;
imgA[i][j].g=111;
imgA[i][j].r=111;
【图像转换】
1)
字节型图像的灰度——彩色转换
cvConcertImage(src,dst,flags=0)
src=float/byte grayscale/color
image
dst=byte grayscale/color
imae
flags=CV_CVTIMG_FLIP //垂直翻转图像
CV_CVTIMG_SWAP_RB //置换R和B通道
2)
彩色图像->灰度图像
cvCvtColor(cimg,gimg,CV_BGR2GRAY)//cimg->gimg
//使用直接转换
for(i=0;iheight;i++)
for(j=0;jwidth;j++)
gimgA[i][j]=(uchar)(cimgA[i][j].b*0.114+ //直接利用转换关系将图像用灰阶表示
cimgA[i][j].g*0.587+
cimgA[i][j].r*0.299);
3)
不同彩色空间之间的转换
cvCvColor(src,dst,code)
code=CV_2
/=RGB,BGR,GRAY,HSV,YCrCb,XYZ,Lab,Luv,HLS
【矩阵处理】
1.
内存的分配与释放
因为OpenCV使用C语言来进行矩阵操作,但是用C++的替代方案可以更加高效地完成操作。
在OpenCV中向量被当做是有一个维数为1的N维矩阵。
矩阵按照行—行方式存储,每行4byte(32bit)对齐。
2.
为新的矩阵分配内存
CvMat
*cvCreateMat(int rows, int cols , int type);
其中Type是矩阵元素的类型:
CV(S|U|F)C方式指定
Eg:
CvMat *
M=cvCreateMat(4,4,CV_32FC1);
3.
释放矩阵内存
CvMat *M=cvCreateMat(4,4,CV_32FC1);
cvReleaseMat(&M);
4.
复制矩阵
CvMat
*M1=cvCreateMat(4,4,CV_32FC1);
cvMat *M2;
M2=cvCloneMat(M1);
5.
初始化矩阵
double a[]={1,2,3,4
5,6,7,8,
9,10,11,12}
CvMat Ma=cvMat(3,4,CV_64FC1,a);
等价于
CvMat Ma;
cvInitMatHeader(&Ma,3,4,CV_64FC1,a);
6.
初始化矩阵为单位矩阵
CvMat
*M=cvCreateMat(4,4,CV_32FC1);
cvSetIdentity(M);
7.
访问矩阵元素
假设我们现在需要访问一个2D浮点型矩阵的第(i,j)个单元
1.
间接访问
cvmSet(M,I,j,2.0);
//设置M的(I,j)位置的值为2.0
t=cvmGet(M,I,j)
2.
直接访问(假设矩阵按照4字节对齐)
CvMat
*M=cvCreateMat(4,4,CV_32FC1);
Int
n =M->cols;
Float *data=
M->data.f1;
Data[i*n+j]=3.0;
3.
直接访问(当数据的对齐可能存在间隙的时候)
CvMat
*M=cvCreateMat(4,4,CV_32FC1);
Int
step=M->Setp/sizeof(float);
Float
*data=M->data.f1;
(data+i*step)[j]=3.0;
4.
对于初始化后的矩阵进行直接访问
double a[16];
CvMat
Ma=cvMat(3,4,CV_64FC1,a);
a[i*4+j]=2.0
【基本运算】
1.
矩阵之间的运算
CvMat *Ma,*Mb,*Mc;
cvAdd(Ma,Mb,Mc);
cvSub(Ma,Mb,Mc);
cvMatMul(Ma,Mb,Mc);
2.
矩阵之间的元素级运算
CvMat *Ma,*Mb,*Mc;
cvMul(Ma,Mb, Mc);
cvDiv(Ma,Mb,Mc);
cvAddS(Ma,cvScalar(-10.0),Mc);
//Ma-10->Mc
3.
向量乘积
double va[]={1,2,3};
double vb[]={0,0,1};
double vc[3];
CvMat
Va=cvMat(3,1,CV_64FC1,va);
CvMat
Vb=cvMat(3,1,CV_64FC1,vb);
CvMat
Vc=cvMat(3,1,CV_64FC1,vc);
Double
res=cvDotProduct(&Va,&Vb);//向量点乘
cvCrossProduct(&Va,&Vb,&vc);//向量叉乘
4.
单一矩阵的运算
CvMat * Ma,* Mb;
cvTranspos(Ma,Mb);//转置transpose(Ma)->Mb(转置不能返回给Ma本身
CvScalar
t=cvTrace(Ma);
cvInvert(Ma,Mb);//逆矩阵
inv(Ma)->Mb
5.
非齐次线性方程求解
cvMat
*A=cvCreateMat(3,3,CV_32FC1);
cvMat
*x=cvCreateMat(3,1,CV_32FC1);
CvMat
*b=cvCreateMat(3,1,CV_32FC1);
cvSolve(&A,&b,&x); //solve(Ax=b)for x
6.
特征值与特征向量(矩阵为方阵)
CvMat
*A=cvCreateMat(3,3,CV_32FC1);
CvMat
*E=cvCreateMat(3,3,CV_32FC1);
CvMat
*I=cvCreateMat(3,1,CV_32FC1);
cvEigenVV(A,E,l);//l=A的特征值(递减顺序)E=对应的特征向量(行向量)
7.
奇异值分解(SVD)
CVMat
*A=cvCreateMat(3,3,CV_32FC1);
CvMat
*U=cvCreateMat(3,3,CV_32FC1);
CvMat *D=cvCreateMat(3,3,CV_32FC1);
CvMat *V=cvCreateMat(3,3,CV_32FC1);
cvSVD(A,D,U,V,CV_SVD_U_T|CV_SVD_V_T);//A=UDV^T
标志位使得矩阵U或者V按照转置的形式返回(若不转置可能运算出错)
【OpenCV视频处理】
1.
从视频流中捕捉一帧画面
(1)
OpenCV支持从摄像头或者视频文件(AVI格式)中捕捉帧画面;
(2)
初始化一个摄像头捕捉器:
CvCapture
*capture=cvCaptureFromCAM(0);
(3)
初始化一个视频文件捕捉器:
CvCapture
*capture=cvCaptureFromAVI(“infile.avi”);
(4)
捕捉一帧画面:
IplImage *img=0;
If(!cvGrabFrame(capture)){
捕捉一帧
Printf(“could not grav a frame\n\7”);
Exit(0);
}
Img=cvRetrieveFrame(capture); //检索捕捉到的这一帧给图像img
若要从多个摄像头中同步捕捉画面,则必须先从每个摄像头中抓取一帧,紧接着要将被捕捉的帧画面恢复到一个IplImage *型图像中。(这个过程可以使用cvQueryFrame()函数一步完成;
(5)
释放视频流捕捉器:
cvReleaseCapture(&capture);
2.
获取/设置视频流信息
(1)
获取视频流设备信息:
CvQueryFrame(capture);//在读取视频流信息之前,要先执行此操作
int frameH=(int)cvGetXaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT);
int frameW=(int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH);
int fps=(int)cvGetCaptureProperty(capture,CV_CAP_PROP_FPS);
int numFrames=(int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_COUNT);
fps仅仅对于视频文件有效,但是不太准确
(2)
获取帧图信息:
float
posMsec=cvGetCaptureProperty(capture,CV_CAP_PROP_POS_MSEC):
int posFrames=(int)
cvGetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES):
float
posRatio=cvGetCaptureProperty(capture,CV_CAP_PROP_POS_AVI_RATIO);
(3)
设置从视频文件抓取第一帧画面的位置:
CvSetCaptureProperty(capture,CV_CAP_PROP_POS_AVI_RATIO,(double)0.9);
3.
保存视频文件
(1)
初始化视频编写器:
CvVideoWriter
*writer=0;
int isColor=1;
int fps=25;
int frameW =640;
int frameH=480;
write=cvCreateVideoWriter(“out.avi”,CV_FOURCC(‘P’,’I’,’M’,’1’),
fps,cvSize(frameW,frameH),isColor)
其他的编码器代号包括:
其它的编码器代号包括:
CV_FOURCC('P','I','M','1') = MPEG-1 codec
CV_FOURCC('M','J','P','G') = motion-jpeg codec (does not work well)
CV_FOURCC('M', 'P', '4', '2') = MPEG-4.2 codec CV_FOURCC('D', 'I',
'V', '3') = MPEG-4.3 codec CV_FOURCC('D', 'I', 'V', 'X') = MPEG-4
codec CV_FOURCC('U', '2', '6', '3') = H263 codec CV_FOURCC('I',
'2', '6', '3') = H263I codec CV_FOURCC('F', 'L', 'V', '1') = FLV1
codec 若编码器代号为 -1,则运行时会弹出一个编码器选择框.
(2)
保持视频文件
IplImage *img=0;
Int nFrames=50;
for(i=0;i
{
cvGrabFrame(capture); //捕捉一帧
img=cvRetrieveFrame(capture);//重新保存这帧图像在img中
//img=cvQueryFrame(capture);
cvWriteFrame(write,img);
}
要查看所抓取到的帧画面,在循环中加入下列语句即可:
cvShowImage(“mainWin”,img);
key=cvWaitKey(20);
注意显示的时候cvWaitkey不能小于20ms,否则画面的显示可能出错。
(3)
释放视频编写器
cvReleaseVideoWriter(&writer);