OpenCV 编程入门


十一月 2006 OpenCV 编程入门 2006年11月22日 OpenCV 编程入门 美国伊力诺理工学院计算机科学系Gady Adam 翻译:Mensch 2006年11月22日 内容 • 简介 • OpenCV概述 • 资料链接 • OpenCV 命名约定 • 编译命令 • C程序实例 • GUI 命令 • 窗口管理 • 输入设备 • OpenCV 基础数据结构 • 图像数据结构 • 矩阵与向量 • 其他数据结构 • 图像处理 • 创建与释放图像结构空间 • 读入与存储图像 • 读取图像元素 • 图像转换 • 绘图命令 • 矩阵操作 • 创建与释放矩阵结构空间 • 读取矩阵元素 • 矩阵/向量操作 • 视频序列处理 • 从视频序列中抓取一帧 • 获取/设定帧信息 • 存储视频文件 简介 Als die Welt noch jung war: Blog 1 OpenCV概述 • 什么是OpenCV • 开源C/C++计算机视觉库. • 面向实时应用进行优化. • 跨操作系统/硬件/窗口管理器. • 通用图像/视频载入、存储和获取. • 由中、高层API构成. • 为Intel®公司的 Integrated Performance Primitives (IPP) 提供了透明接口. • 特性: • 图像数据操作 (分配,释放, 复制, 设定, 转换). • 图像与视频 I/O (基于文件/摄像头输入, 图像/视频文件输出). • 矩阵与向量操作与线性代数计算(相乘, 求解, 特征值, 奇异值分解SVD). • 各种动态数据结构(列表, 队列, 集, 树, 图). • 基本图像处理(滤波, 边缘检测, 角点检测, 采样与插值, 色彩转换, 形态操作, 直方图, 图像金字塔). • 结构分析(连接成分, 轮廓处理, 距离转换, 模板匹配, Hough转换, 多边形近似, 线性拟合, 椭圆拟合, Delaunay三角化). • 摄像头标定 (寻找并跟踪标定模板, 标定, 基础矩阵估计, homography估计, 立体匹配). • 动作分析(光流, 动作分割, 跟踪). • 对象辨识 (特征方法, 隐马可夫链模型HMM). • 基本GUI(显示图像/视频, 键盘鼠标操作, 滚动条). • 图像标识 (直线, 圆锥, 多边形, 文本绘图) • OpenCV 模块: • cv - OpenCV 主要函数. • cvaux - 辅助 (实验性) OpenCV 函数. • cxcore - 数据结构与线性代数算法. • highgui - GUI函数. 资料链接 • 参考手册: • /docs/index.htm • 网络资源: • 官方网页: http://www.intel.com/technology/computing/opencv/ • 软件下载: http://sourceforge.net/projects/opencvlibrary/ • 书籍: • Open Source Computer Vision Library by Gary R. Bradski, Vadim Pisarevsky, and Jean-Yves Bouguet, Springer, 1st ed. (June, 2006). • 视频处理例程 (位于 /samples/c/目录中): • 色彩跟踪: camshiftdemo • 点跟踪: lkdemo • 动作分割: motempl • 边缘检测: laplace • 图像处理例程(位于/samples/c/目录中): • 边缘检测: edge • 分割: pyramid_segmentation • 形态: morphology • 直方图: demhist • 距离转换: distrans Als die Welt noch jung war: Blog 2 • 椭圆拟合 fitellipse OpenCV 命名约定 • 函数命名: cvActionTarget[Mod](...) Action = 核心功能(例如 设定set, 创建create) Target = 操作目标 (例如 轮廓contour, 多边形polygon) [Mod] = 可选修饰词 (例如说明参数类型) • 矩阵数据类型: CV_(S|U|F)C S = 带符号整数 U = 无符号整数 F = 浮点数 例: CV_8UC1 表示一个8位无符号单通道矩阵, CV_32FC2 表示一个32位浮点双通道矩阵. • 图像数据类型: IPL_DEPTH_(S|U|F) 例: IPL_DEPTH_8U 表示一个8位无符号图像. IPL_DEPTH_32F 表示一个32位浮点数图像. • 头文件: #include #include #include #include // 不必要 - 该头文件已在 cv.h 文件中包含 编译命令 • Linux系统: g++ hello-world.cpp -o hello-world \ -I /usr/local/include/opencv -L /usr/local/lib \ -lm -lcv -lhighgui -lcvaux • Windows系统: 注意在项目属性中设好OpenCV头文件以及库文件的路径. C程序实例 //////////////////////////////////////////////////////////////////////// // // hello-world.cpp // // 一个简单的OpenCV程序 // 它从一个文件中读取图像,将色彩值颠倒,并显示结果. // //////////////////////////////////////////////////////////////////////// #include #include #include Als die Welt noch jung war: Blog 3 #include #include int main(int argc, char *argv[]) { IplImage* img = 0; int height,width,step,channels; uchar *data; int i,j,k; if(argc<2){ printf("Usage: main \n\7"); exit(0); } // 载入图像 img=cvLoadImage(argv[1]); if(!img){ printf("Could not load image file: %s\n",argv[1]); exit(0); } // 获取图像数据 height = img->height; width = img->width; step = img->widthStep; channels = img->nChannels; data = (uchar *)img->imageData; printf("Processing a %dx%d image with %d channels\n",height,width,channels); // 创建窗口 cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE); cvMoveWindow("mainWin", 100, 100); // 反色图像 for(i=0;i0 载入图像转为三通道彩色图像 =0 载入图像转为单通道灰度图像 <0 不转换载入图像(通道数与图像文件相同). • 图像存储为图像文件: if(!cvSaveImage(outFileName,img)) printf("Could not save: %s\n",outFileName); 输入文件格式由文件扩展名决定. 存取图像元素 • 假设需要读取在i行j列像点的第k通道. 其中, 行数i的范围为[0, height-1], 列数j的范围为[0, width-1], 通道k的范 围为[0, nchannels-1]. • ????: (比较通用, 但效率低, 可读取任一类型图像数据) • 对单通道字节图像: IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); CvScalar s; s=cvGet2D(img,i,j); // get the (i,j) pixel value printf("intensity=%f\n",s.val[0]); s.val[0]=111; cvSet2D(img,i,j,s); // set the (i,j) pixel value • 对多通道浮点或字节图像: Als die Welt noch jung war: Blog 9 IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3); CvScalar s; s=cvGet2D(img,i,j); // get the (i,j) pixel value 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); // set the (i,j) pixel value • ????: (效率高, 但容易出错) • 对单通道字节图像: 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_8U,3); ((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 0]=111; // B ((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 1]=112; // G ((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 2]=113; // R • 对多通道浮点图像: 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 • ??????? : (在某些情况下简单高效) • 对单通道字节图像: IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); int height = img->height; int width = img->width; int step = img->widthStep/sizeof(uchar); uchar* data = (uchar *)img->imageData; data[i*step+j] = 111; • 对多通道字节图像: IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3); int height = img->height; int width = img->width; int step = img->widthStep/sizeof(uchar); int channels = img->nChannels; uchar* data = (uchar *)img->imageData; data[i*step+j*channels+k] = 111; • 对单通道浮点图像(假设用4字节调整): IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3); int height = img->height; int width = img->width; int step = img->widthStep/sizeof(float); int channels = img->nChannels; float * data = (float *)img->imageData; data[i*step+j*channels+k] = 111; • ?? c++ wrapper ??????: (简单高效) • 对单/多通道字节图像,多通道浮点图像定义一个 c++ wrapper: template class Image { Als die Welt noch jung war: Blog 10 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; 图像转换 • 转为灰度或彩色字节图像: cvConvertImage(src, dst, flags=0); src = float/byte grayscale/color image dst = byte grayscale/color image flags = CV_CVTIMG_FLIP (flip vertically) CV_CVTIMG_SWAP_RB (swap the R and B channels) • 转换彩色图像为灰度图像: 使用OpenCV转换函数: cvCvtColor(cimg,gimg,CV_BGR2GRAY); // cimg -> gimg Als die Welt noch jung war: Blog 11 直接转换: 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); • 颜色空间转换: cvCvtColor(src,dst,code); // src -> dst code = CV_2 / = RGB, BGR, GRAY, HSV, YCrCb, XYZ, Lab, Luv, HLS e.g.: CV_BGR2GRAY, CV_BGR2HSV, CV_BGR2Lab 绘图命令 • 画长方体: // 用宽度为1的红线在(100,100)与(200,200)之间画一长方体 cvRectangle(img, cvPoint(100,100), cvPoint(200,200), cvScalar(255,0,0), 1); • 画圆: // 在(100,100)处画一半径为20的圆,使用宽度为1的绿线 cvCircle(img, cvPoint(100,100), 20, cvScalar(0,255,0), 1); • 画线段: // 在(100,100)与(200,200)之间画绿色线段,宽度为1 cvLine(img, cvPoint(100,100), cvPoint(200,200), cvScalar(0,255,0), 1); • 画一组线段: CvPoint curve1[]={10,10, 10,100, 100,100, 100,10}; CvPoint curve2[]={30,30, 30,130, 130,130, 130,30, 150,10}; CvPoint* curveArr[2]={curve1, curve2}; int nCurvePts[2]={4,5}; int nCurves=2; int isCurveClosed=1; int lineWidth=1; cvPolyLine(img,curveArr,nCurvePts,nCurves,isCurveClosed,cvScalar(0,255,255),lineWidth); • 画内填充色的多边形: cvFillPoly(img,curveArr,nCurvePts,nCurves,cvScalar(0,255,255)); • 添加文本: CvFont font; double hScale=1.0; double vScale=1.0; int lineWidth=1; cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, hScale,vScale,0,lineWidth); cvPutText (img,"My comment",cvPoint(200,400), &font, cvScalar(255,255,0)); Other possible fonts: CV_FONT_HERSHEY_SIMPLEX, CV_FONT_HERSHEY_PLAIN, CV_FONT_HERSHEY_DUPLEX, CV_FONT_HERSHEY_COMPLEX, Als die Welt noch jung war: Blog 12 CV_FONT_HERSHEY_TRIPLEX, CV_FONT_HERSHEY_COMPLEX_SMALL, CV_FONT_HERSHEY_SCRIPT_SIMPLEX, CV_FONT_HERSHEY_SCRIPT_COMPLEX, 矩阵操作 分配释放矩阵空间 • 综述: • OpenCV有针对矩阵操作的C语言函数. 许多其他方法提供了更加方便的C++接口,其效率与 OpenCV一样. • OpenCV将向量作为1维矩阵处理. • 矩阵按行存储,每行有4字节的校整. • 分配矩阵空间: CvMat* cvCreateMat(int rows, int cols, int type); type: 矩阵元素类型. 格式为CV_(S|U|F)C. 例如: CV_8UC1 表示8位无符号单通道矩阵, CV_32SC2表示32位有符号双通道矩阵. 例程: CvMat* M = cvCreateMat(4,4,CV_32FC1); • 释放矩阵空间: CvMat* M = cvCreateMat(4,4,CV_32FC1); cvReleaseMat(&M); • 复制矩阵: CvMat* M1 = cvCreateMat(4,4,CV_32FC1); CvMat* M2; M2=cvCloneMat(M1); • 初始化矩阵: 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); • 初始化矩阵为单位阵: CvMat* M = cvCreateMat(4,4,CV_32FC1); cvSetIdentity(M); // 这里似乎有问题,不成功 存取矩阵元素 • 假设需要存取一个2维浮点矩阵的第(i,j)个元素. • 间接存取矩阵元素: cvmSet(M,i,j,2.0); // Set M(i,j) t = cvmGet(M,i,j); // Get M(i,j) Als die Welt noch jung war: Blog 13 • 直接存取,假设使用4-字节校正: CvMat* M = cvCreateMat(4,4,CV_32FC1); int n = M->cols; float *data = M->data.fl; data[i*n+j] = 3.0; • 直接存取,校正字节任意: CvMat* M = cvCreateMat(4,4,CV_32FC1); int step = M->step/sizeof(float); float *data = M->data.fl; (data+i*step)[j] = 3.0; • 直接存取一个初始化的矩阵元素: double a[16]; CvMat Ma = cvMat(3, 4, CV_64FC1, a); a[i*4+j] = 2.0; // Ma(i,j)=2.0; 矩阵/向量操作 • 矩阵-矩阵操作: CvMat *Ma, *Mb, *Mc; cvAdd(Ma, Mb, Mc); // Ma+Mb -> Mc cvSub(Ma, Mb, Mc); // Ma-Mb -> Mc cvMatMul(Ma, Mb, Mc); // Ma*Mb -> Mc • 按元素的矩阵操作: CvMat *Ma, *Mb, *Mc; cvMul(Ma, Mb, Mc); // Ma.*Mb -> Mc cvDiv(Ma, Mb, Mc); // Ma./Mb -> Mc cvAddS(Ma, cvScalar(-10.0), Mc); // Ma.-10 -> Mc • 向量乘积: 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); // 点乘: Va . Vb -> res cvCrossProduct(&Va, &Vb, &Vc); // 向量积: Va x Vb -> Vc end{verbatim} 注意 Va, Vb, Vc 在向量积中向量元素个数须相同. • 单矩阵操作: CvMat *Ma, *Mb; cvTranspose(Ma, Mb); // transpose(Ma) -> Mb (不能对自身进行转置) CvScalar t = cvTrace(Ma); // trace(Ma) -> t.val[0] double d = cvDet(Ma); // det(Ma) -> d cvInvert(Ma, Mb); // inv(Ma) -> Mb • 非齐次线性系统求解: Als die Welt noch jung war: Blog 14 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 • 特征值分析(针对对称矩阵): CvMat* A = cvCreateMat(3,3,CV_32FC1); CvMat* E = cvCreateMat(3,3,CV_32FC1); CvMat* l = cvCreateMat(3,1,CV_32FC1); cvEigenVV(&A, &E, &l); // l = A的特征值 (降序排列) // E = 对应的特征向量 (每行) • 奇异值分解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 = U D V^T 标号使得 U 和 V 返回时被转置(若没有转置标号,则有问题不成功!!!). 视频序列操作 从视频序列中抓取一帧 • OpenCV支持从摄像头或视频文件(AVI)中抓取图像. • 从摄像头获取初始化: CvCapture* capture = cvCaptureFromCAM(0); // capture from video device #0 • 从视频文件获取初始化: CvCapture* capture = cvCaptureFromAVI("infile.avi"); • 抓取帧: IplImage* img = 0; if(!cvGrabFrame(capture)){ // 抓取一帧 printf("Could not grab a frame\n\7"); exit(0); } img=cvRetrieveFrame(capture); // 恢复获取的帧图像 要从多个摄像头同时获取图像, 首先从每个摄像头抓取一帧. 在抓取动作都结束后再恢复帧图像. • 释放抓取源: cvReleaseCapture(&capture); 注意由设备抓取的图像是由capture函数自动分配和释放的. 不要试图自己释放它. 获取/设定帧信息 • 获取设备特性: cvQueryFrame(capture); // this call is necessary to get correct // capture properties int frameH = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT); int frameW = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH); Als die Welt noch jung war: Blog 15 int fps = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FPS); int numFrames = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_COUNT); 所有帧数似乎只与视频文件有关. 用摄像头时不对,奇怪!!!. • 获取帧信息: 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); 获取所抓取帧在视频序列中的位置, 从首帧开始按[毫秒]算. 或者从首帧开始从0标号, 获取所抓取帧的标号. 或 者取相对位置,首帧为0,末帧为1, 只对视频文件有效. • 设定所抓取的第一帧标号: // 从视频文件相对位置0.9处开始抓取 cvSetCaptureProperty(capture, CV_CAP_PROP_POS_AVI_RATIO, (double)0.9); 只对从视频文件抓取有效. 不过似乎也不成功!!! 存储视频文件 • 初始化视频存储器: CvVideoWriter *writer = 0; int isColor = 1; int fps = 25; // or 30 int frameW = 640; // 744 for firewire cameras int frameH = 480; // 480 for firewire cameras writer=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则将打开一个编码选择窗口(windows系统下). • 存储视频文件: IplImage* img = 0; int nFrames = 50; for(i=0;i
还剩16页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

bmbm

贡献于2015-11-07

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf