<4D F736F F D204F70656E4356B1E0B3CCB2CEBFBCCAD6B2E1>

Size: px
Start display at page:

Download "<4D F736F F D204F70656E4356B1E0B3CCB2CEBFBCCAD6B2E1>"

Transcription

1 OpenCV 参考手册 OPENCV 参考手册...1 OPENCV 编程简介 ( 矩阵 / 图像 / 视频的基本读写操作 )...23 INTRODUCTION TO PROGRAMMING WITH OPENCV...23 OPENCV 编程简介...23 一 简介 OPENCV 的特点...23 (1) 总体描述...23 (2) 功能...23 (3) OpenCV 模块 有用的学习资源...25 (1) 参考手册 :...25 (2) 网络资源 :...25 (3) 书籍 :...25 (4) 视频处理例程 ( 在 <opencv root>/samples/c/):...25 (5) 图像处理例程 ( 在 <opencv root>/samples/c/): OPENCV 命名规则...25 (1) 函数名 :...26 (2) 矩阵数据类型 :...26 S = 符号整型...26 (3) 图像数据类型 :...26 (4) 头文件 : 编译建议...26 (1) Linux:...26 (2) Windows: C 例程...26 二 GUI 指令 窗口管理...28 (1) 创建和定位一个新窗口 :...28 (2) 载入图像 :...28 (3) 显示图像 :...28 (4) 关闭窗口 :...28 (5) 改变窗口大小 : 输入处理...28 (1) 处理鼠标事件 :...28 (2) 处理键盘事件 :...29 (3) 处理滑动条事件 :...30 三 OPENCV 的基本数据结构

2 1 图像数据结构...30 (1) IPL 图像 : 矩阵与向量...31 (1) 矩阵 :...31 (2) 一般矩阵 :...32 (3) 标量 : 其它结构类型...32 (1) 点 :...32 (2) 矩形框大小 ( 以像素为精度 ):...32 (3) 矩形框的偏置和大小 :...32 四 图像处理 图像的内存分配与释放...33 (1) 分配内存给一幅新图像 :...33 (2) 释放图像 :...33 (3) 复制图像 :...33 (4) 设置 / 获取感兴趣区域 ROI:...33 (5) 设置 / 获取感兴趣通道 COI: 图像读写...34 (1) 从文件中读入图像 :...34 (2) 保存图像 : 访问图像像素...34 (1) 假设你要访问第 k 通道 第 i 行 第 j 列的像素...34 (2) 间接访问 : ( 通用, 但效率低, 可访问任意格式的图像 )...34 (3) 直接访问 : ( 效率高, 但容易出错 )...35 (4) 基于指针的直接访问 : ( 简单高效 )...35 (5) 基于 c++ wrapper 的直接访问 : ( 更简单高效 ) 图像转换...37 (1) 字节型图像的灰度 彩色转换 :...37 (2) 彩色图像 > 灰度图像 :...37 (3) 不同彩色空间之间的转换 : 绘图指令...37 (1) 绘制矩形 :...38 (2) 绘制圆形 :...38 (3) 绘制线段 :...38 (4) 绘制一组线段 :...38 (5) 绘制一组填充颜色的多边形 :...38 (6) 文本标注 :...39 五 矩阵处理 矩阵的内存分配与释放...39 (1) 总体上 :...39 (2) 为新矩阵分配内存 :...39 (3) 释放矩阵内存 :...40 (4) 复制矩阵 :

3 (5) 初始化矩阵 :...40 (6) 初始化矩阵为单位矩阵 : 访问矩阵元素...40 (1) 假设需要访问一个 2D 浮点型矩阵的第 (i, j) 个单元...40 (2) 间接访问 :...40 (3) 直接访问 ( 假设矩阵数据按 4 字节行对齐 ):...40 (4) 直接访问 ( 当数据的行对齐可能存在间隙时 possible alignment gaps):...41 (5) 对于初始化后的矩阵进行直接访问 : 矩阵/ 向量运算...41 (1) 矩阵之间的运算 :...41 (2) 矩阵之间的元素级运算 :...41 (3) 向量乘积 :...41 (4) 单一矩阵的运算 :...41 (5) 非齐次线性方程求解 :...42 (6) 特征值与特征向量 ( 矩阵为方阵 ):...42 (7) 奇异值分解 (SVD):====...42 六 视频处理 从视频流中捕捉一帧画面...42 (1) OpenCV 支持从摄像头或视频文件 (AVI 格式 ) 中捕捉帧画面...42 (2) 初始化一个摄像头捕捉器 :...42 (3) 初始化一个视频文件捕捉器 :...42 (4) 捕捉一帧画面 :...42 (5) 释放视频流捕捉器 : 获取/ 设置视频流信息...43 (1) 获取视频流设备信息 :...43 (2) 获取帧图信息 :...43 (3) 设置从视频文件抓取的第一帧画面的位置 : 保存视频文件...44 (1) 初始化视频编写器 :...44 (2) 保持视频文件 :...44 (3) 释放视频编写器 :...44 OPENCV 概述...45 Wikipedia, 自由的百科全书...45 什么是 OPENCV...45 重要特性...45 谁创建了它...45 新特征...45 从哪里下载 OPENCV...45 如果在安装 / 运行 / 使用 OPENCV 中遇到问题...45 OPENCV 参考手册...46 FAQ 中文 安装配置问题

4 1.1 缺少 HIGHGUI100.DLL 使用库的技术问题 视频读写出现问题 怎么访问图像像素 如何访问矩阵元素? 如何在 OPENCV 中处理我自己的数据 如何读入和显示图像 如何检测和处理轮廓线...49 // TGRAY(X,Y) = GRAY(X,Y) < (L+1)*255/N? 255 : // SQUARE CONTOURS SHOULD HAVE 4 VERTICES AFTER APPROXIMATION 如何用 OPENCV 来定标摄像机...54 CXCORE 库的框架概述 CXCORE 基础结构...57 CVPOINT...57 CVPOINT;...57 CVPOINT2D32F...57 CVPOINT2D32F;...57 CVPOINT3D32F...58 CVPOINT3D32F;...58 CVSIZE...58 CVSIZE;...58 CVSIZE2D32F...58 CVSIZE2D32F;...59 CVSIZE2D32F S;...59 CVRECT...59 CVRECT;...59 CVSCALAR...59 CVSCALAR;...60 CVTERMCRITERIA...60 CVTERMCRITERIA;...61 CVMAT...61 CVMATND...62 CVSPARSEMAT...62 IPLIMAGE

5 IPL 图像头...63 CVARR CXCORE 数组操作...65 初始化...65 CREATEIMAGE...65 CreateImageHeader...65 ReleaseImageHeader...66 ReleaseImage...66 InitImageHeader...67 CloneImage...67 SetImageCOI...68 GetImageCOI...68 SetImageROI...68 ResetImageROI...69 GetImageROI...69 CreateMat...69 CreateMatHeader...70 ReleaseMat...70 InitMatHeader...70 Mat...72 CloneMat...72 CreateMatND...72 CreateMatNDHeader...73 ReleaseMatND...73 InitMatNDHeader...74 CloneMatND...74 DecRefData...74 IncRefData...75 CreateData...75 ReleaseData...75 SetData...75 GetRawData...76 GetMat...77 GetImage...77 CreateSparseMat...78 ReleaseSparseMat...78 CloneSparseMat...78 获取元素和数组子集...78 GetSubRect...79 GetRow, GetRows...79 GetCol, GetCols...80 GetDiag

6 GetSize...80 InitSparseMatIterator...81 GetNextSparseNode...81 GetElemType...82 GetDims, GetDimSize...82 Ptr*D...83 Get*D...84 GetReal*D...84 mget...85 Set*D...85 SetReal*D...86 mset...86 ClearND...87 拷贝和添加...87 Copy...87 Set...87 SetZero...88 SetIdentity...88 Range...88 变换和置换...89 Reshape...89 ReshapeMatND...90 Repeat...91 Flip...91 Split...92 Merge...92 MixChannels...93 RandShuffle...93 算术, 逻辑和比较...94 LUT...94 ConvertScale...94 ConvertScaleAbs...95 Add...95 AddS...96 AddWeighted...96 Sub...97 SubS...98 SubRS...98 Mul...98 Div...99 And...99 AndS Or OrS

7 Xor XorS Not Cmp CmpS InRange InRangeS Max MaxS Min MinS AbsDiff AbsDiffS 统计 CountNonZero Sum SC = SUMI ARR(I)C Avg AvgSdv MinMaxLoc Norm Reduce 线性代数 DotProduct Normalize CrossProduct ScaleAdd GEMM Transform PerspectiveTransform MulTransposed Trace Transpose Det Invert Solve SVD SVBkSb EigenVV CalcCovarMatrix Mahalanobis CalcPCA ProjectPCA BackProjectPCA

8 数学函数 Round, Floor, Ceil Sqrt InvSqrt Cbrt FastArctan IsNaN IsInf CartToPolar PolarToCart Pow Exp Log SolveCubic 随机数生成 RNG RandArr RandInt RandReal 离散变换 DFT GetOptimalDFTSize MulSpectrums DCT CXCORE 动态结构 内存存储 (MEMORY STORAGE) CvMemStorage CvMemBlock CvMemStoragePos CreateMemStorage CreateChildMemStorage ReleaseMemStorage ClearMemStorage MemStorageAlloc MemStorageAllocString SaveMemStoragePos RestoreMemStoragePos 序列 CvSeq CvSeqBlock CvSlice CreateSeq SetSeqBlockSize SeqPush

9 SeqPop SeqPushFront SeqPopFront SeqPushMulti SeqPopMulti SeqInsert SeqRemove ClearSeq GetSeqElem SeqElemIdx cvseqtoarray MakeSeqHeaderForArray SeqSlice CloneSeq SeqRemoveSlice SeqInsertSlice SeqInvert SeqSort SeqSearch StartAppendToSeq StartWriteSeq EndWriteSeq FlushSeqWriter StartReadSeq GetSeqReaderPos SetSeqReaderPos 集合 CvSet CreateSet SetAdd SetRemove SetNew SetRemoveByPtr GetSetElem ClearSet 图 CvGraph CreateGraph GraphAddVtx GraphRemoveVtx GraphRemoveVtxByPtr GetGraphVtx GraphVtxIdx GraphAddEdge

10 GraphAddEdgeByPtr GraphRemoveEdge GraphRemoveEdgeByPtr FindGraphEdge FindGraphEdgeByPtr GraphEdgeIdx GraphVtxDegree GraphVtxDegreeByPtr ClearGraph CloneGraph CvGraphScanner StartScanGraph NextGraphItem ReleaseGraphScanner 树 CV_TREE_NODE_FIELDS CvTreeNodeIterator InitTreeNodeIterator NextTreeNode PrevTreeNode TreeToNodeSeq InsertNodeIntoTree RemoveNodeFromTree CXCORE 绘图函数 曲线与形状 CV_RGB Line Rectangle Circle Ellipse EllipseBox FillPoly FillConvexPoly PolyLine 文本 InitFont PutText GetTextSize 点集和轮廓 DrawContours InitLineIterator ClipLine Ellipse2Poly

11 5.CXCORE 数据保存和运行时类型信息 文件存储 CvFileStorage CvFileNode CvAttrList OpenFileStorage ReleaseFileStorage 写数据 StartWriteStruct EndWriteStruct WriteInt WriteReal WriteString WriteComment StartNextStream Write WriteRawData WriteFileNode 读取数据 GetRootFileNode GetFileNodeByName GetHashedKey GetFileNode GetFileNodeName ReadInt ReadIntByName ReadReal ReadRealByName ReadString ReadStringByName Read ReadByName ReadRawData StartReadRawData ReadRawDataSlice 运行时类型信息和通用函数 CvTypeInfo RegisterType UnregisterType FirstType FindType TypeOf Release Clone

12 Save Load CXCORE 其它混合函数 CheckArr KMeans SeqPartition CXCORE 错误处理和系统函数 错误处理 GetErrStatus SetErrStatus GetErrMode SetErrMode Error ErrorStr RedirectError cvnuldevreport cvstderrreport cvguiboxreport 系统函数 Alloc Free GetTickCount GetTickFrequency RegisterModule GetModuleInfo UseOptimized SetMemoryManager SetIPLAllocators 机器学习中文参考手册 简介 : 通用类和函数 CvStatModel CvStatModel::CvStatModel CvStatModel::CvStatModel(...) CvStatModel:: ~CvStatModel CvStatModel::clear CvStatModel::save CvStatModel::load CvStatModel::write CvStatModel::read CvStatModel::train CvStatModel::predict NORMAL BAYES 分类器 CvNormalBayesClassifier CvNormalBayesClassifier::train

13 2.3 CvNormalBayesClassifier::predict K 近邻算法 CvKNearest CvKNearest::train CvKNearest::find_nearest 例程 : 使用 knn 进行 2 维样本集的分类, 样本集的分布为混合高斯分布 CVMAT TRAINDATA1, TRAINDATA2, TRAINCLASSES1, TRAINCLASSES2; 支持向量机部分 CvSVM CvSVMParams CvSVM::train CvSVM::get_support_vector* 补充 : 在 WindowsXP+OpenCVRC1 平台下整合 OpenCV 与 libsvm 常用 libsvm 资料链接 决策树 TRAINING DECISION TREES CvDTreeSplit CvDTreeNode CvDTreeParams CvDTreeTrainData CvDTree CvDTree::train CvDTree::predict BOOSTING CvBoostParams CvBoostTree CvBoost CvBoost::train CvBoost::predict CvBoost::prune CvBoost::get_weak_predictors RANDOM TREES CvRTrees::train Trains Random Trees model CvRTrees::predict Predicts the output for the input sample CvRTrees::get_var_importance Retrieves the variable importance array CvRTrees::get_proximity Retrieves proximitity measure between two training samples EXPECTATION MAXIMIZATION CvEMParams nclusters cov_mat_type start_step term_crit probs

14 8.7 weights covs means 神经网络 CVAUX 中文参考手册 立体匹配 FindStereoCorrespondence VIEW MORPHING FUNCTIONS MakeScanlines PreWarpImage FindRuns DynamicCorrespondMulti MakeAlphaScanlines MorphEpilinesMulti PostWarpImage DeleteMoire img:: Image THE FUNCTION D TRACKING FUNCTIONS dTrackerCalibrateCameras dTrackerLocateObjects EIGEN OBJECTS (PCA) FUNCTIONS CalcCovarMatrixEx IPLIMAGE CalcEigenObjects CalcDecompCoeff EigenDecomposite EigenProjection EMBEDDED HIDDEN MARKOV MODELS FUNCTIONS CvHMM CvImgObsInfo Create2DHMM Release2DHMM CreateObsInfo ReleaseObsInfo ImgToObs_DCT UniformImgSegm InitMixSegm EstimateHMMStateParams EstimateTransProb EstimateObsProb EViterbi

15 5.14 MixSegmL CVVIMAGE 类参考手册 CVVIMAGE 使用说明和注意事项 CVVIMAGE::CREATE CVVIMAGE::COPYOF CVVIMAGE::LOAD CVVIMAGE::LOADRECT CVVIMAGE::SAVE CVVIMAGE::SHOW CVVIMAGE::SHOW CVVIMAGE::DRAWTOHDC CVVIMAGE::FILL CVVIMAGE 类定义 CVIMAGE 类参考手册 CVIMAGE::CVIMAGE CVIMAGE::~CVIMAGE CVIMAGE::CLONE CVIMAGE::CREATE CVIMAGE::RELEASE CVIMAGE::CLEAR CVIMAGE::ATTACH CVIMAGE::DETACH CVIMAGE::LOAD CVIMAGE::READ CVIMAGE::SAVE CVIMAGE::WRITE CVIMAGE::SHOW CVIMAGE::IS_VALID CVIMAGE::WIDTH CVIMAGE::HEIGHT CVIMAGE::SIZE CVIMAGE::ROI_SIZE CVIMAGE::ROI CVIMAGE::COI CVIMAGE::SET_ROI CVIMAGE::RESET_ROI CVIMAGE::SET_COI CVIMAGE::DEPTH CVIMAGE::CHANNELS CVIMAGE::PIX_SIZE CVIMAGE::DATA CVIMAGE::STEP CVIMAGE::ORIGIN

16 30.CVIMAGE::ROI_ROW 运算符重载 CVIMAGE 中的陷阱和 BUG CVIMAGE 类的定义 关于引用计数 CVIMAGE 中的引用计数机制 CVIMAGE(IPLIMAGE* IMG) 陷阱 ATTACH 问题 重载操作符 = 时的内存泄漏 小节 附 : 修复的 CVIMAGE 相关页面 CV 图像处理 梯度 边缘和角点 Sobel Laplace Canny PreCornerDetect CornerEigenValsAndVecs CornerMinEigenVal CornerHarris FindCornerSubPix GoodFeaturesToTrack 采样 插值和几何变换 InitLineIterator SampleLine GetRectSubPix GetQuadrangleSubPix Resize WarpAffine GetAffineTransform DRotationMatrix WarpPerspective WarpPerspectiveQMatrix GetPerspectiveTransform Remap LogPolar 形态学操作 CreateStructuringElementEx ReleaseStructuringElement Erode Dilate MorphologyEx

17 滤波器与色彩空间变换 Smooth Filter2D CopyMakeBorder Integral CvtColor Threshold AdaptiveThreshold 金字塔及其应用 PyrDown PyrUp 连接部件 CvConnectedComp FloodFill FindContours StartFindContours FindNextContour SubstituteContour EndFindContours PyrSegmentation PyrMeanShiftFiltering Watershed 图像与轮廓矩 Moments GetSpatialMoment GetCentralMoment GetNormalizedCentralMoment GetHuMoments 特殊图像变换 HoughLines HoughCircles DistTransform Inpaint 直方图 CvHistogram CreateHist SetHistBinRanges ReleaseHist ClearHist MakeHistHeaderForArray QueryHistValue_1D GetHistValue_1D GetMinMaxHistValue NormalizeHist

18 ThreshHist CompareHist CopyHist CalcHist CalcBackProject CalcBackProjectPatch CalcProbDensity EqualizeHist 匹配 MatchTemplate MatchShapes CalcEMD CV 结构分析 轮廓处理函数 ApproxChains StartReadChainPoints ReadChainPoint ApproxPoly BoundingRect ContourArea ArcLength CreateContourTree ContourFromContourTree MatchContourTrees 计算几何 MaxRect CvBox2D PointSeqFromMat BoxPoints FitEllipse FitLine ConvexHull CheckContourConvexity CvConvexityDefect ConvexityDefects PointPolygonTest MinAreaRect MinEnclosingCircle CalcPGH 平面划分 CvSubdiv2D CvQuadEdge2D CvSubdiv2DPoint Subdiv2DGetEdge

19 Subdiv2DRotateEdge Subdiv2DEdgeOrg Subdiv2DEdgeDst CreateSubdivDelaunay2D SubdivDelaunay2DInsert Subdiv2DLocate FindNearestPoint2D CalcSubdivVoronoi2D ClearSubdivVoronoi2D CV 运动分析与对象跟踪 背景统计量的累积 Acc SquareAcc MultiplyAcc RunningAvg 运动模板 UpdateMotionHistory CalcMotionGradient CalcGlobalOrientation SegmentMotion 对象跟踪 MeanShift CamShift SnakeImage 光流 CalcOpticalFlowHS CalcOpticalFlowLK CalcOpticalFlowBM CalcOpticalFlowPyrLK 预估器 CvKalman CreateKalman ReleaseKalman KalmanPredict KALMAN 滤波器状态 KalmanCorrect CvConDensation CreateConDensation ReleaseConDensation ConDensInitSampleSet ConDensUpdateByTime CV 模式识别

20 CvHaarFeature, CvHaarClassifier, CvHaarStageClassifier, CvHaarClassifierCascade cvloadhaarclassifiercascade cvreleasehaarclassifiercascade cvhaardetectobjects cvsetimagesforhaarclassifiercascade cvrunhaarclassifiercascade CV 照相机定标和三维重建 针孔相机模型和变形 照相机定标 ProjectPoints FindHomography CalibrateCamera FindExtrinsicCameraParams Rodrigues Undistort InitUndistortMap FindChessboardCorners DrawChessBoardCorners 姿态估计 CreatePOSITObject POSIT POSIT 迭代算法程序终止的条件 ReleasePOSITObject CalcImageHomography 对极几何 ( 双视几何 ) FindFundamentalMat ComputeCorrespondEpilines ConvertPointsHomogenious HIGHGUI 中文参考手册 HIGHGUI 概述 HIGHGUI 简单图形界面 CVNAMEDWINDOW CVDESTROYWINDOW CVDESTROYALLWINDOWS CVRESIZEWINDOW CVMOVEWINDOW CVGETWINDOWHANDLE CVGETWINDOWNAME CVSHOWIMAGE CVCREATETRACKBAR CVGETTRACKBARPOS

21 CVSETTRACKBARPOS CVSETMOUSECALLBACK CVWAITKEY HIGHGUI 读取与保存图像 CVLOADIMAGE CVSAVEIMAGE HIGHGUI 视频读写函数 CVCAPTURE CVCREATEFILECAPTURE CVCREATECAMERACAPTURE CVRELEASECAPTURE CVGRABFRAME CVRETRIEVEFRAME CVQUERYFRAME CVGETCAPTUREPROPERTY CV_CAP_PROP_POS_MSEC 影片目前位置, 为毫秒数或者视频获取时间戳 CVSETCAPTUREPROPERTY CVCREATEVIDEOWRITER CVRELEASEVIDEOWRITER CVWRITEFRAME HIGHGUI 实用函数与系统函数 CVINITSYSTEM CVCONVERTIMAGE OPENCV 编码样式指南 前言 文件命名 文件结构 命名约定 函数接口设计 OPENCV 的 PYTHON 接口 PYTHON 接口的差异 没有 IplImage 迭代访问 切片方式 CvMatND CvSeq 交互命令行编程 Matlab 语法

22 22

23 OpenCV 编程简介 ( 矩阵 / 图像 / 视频的基本读写操作 ) Introduction to programming with OpenCV OpenCV 编程简介作者 : Gady Agam Department of Computer Science January 27, 2006 Illinois Institute of Technology URL: ION 翻译 : chenyusiyuan January 26, 摘要 : 本文旨在帮助读者快速入门 OpenCV, 而无需阅读冗长的参考手册 掌握了 OpenCV 的以下基础知识后, 有需要的话再查阅相关的参考手册 一 简介 1 OpenCV 的特点 (1) 总体描述 OpenCV 是一个基于 C/C++ 语言的开源图像处理函数库其代码都经过优化, 可用于实时处理图像具有良好的可移植性可以进行图像 / 视频载入 保存和采集的常规操作具有低级和高级的应用程序接口 (API) 提供了面向 Intel IPP 高效多媒体函数库的接口, 可针对你使用的 Intel CPU 优化代码, 提高程序性能 ( 译注 :OpenCV 2.0 版的代码已显着优化, 无需 IPP 来提升性能, 故 2.0 版不再提供 IPP 接口 ) (2) 功能 图像数据操作 ( 内存分配与释放, 图像复制 设定和转换 ) conversion). Image data manipulation (allocation, release, copying, setting, 图像 / 视频的输入输出 ( 支持文件或摄像头的输入, 图像 / 视频文件的输出 ) output). Image and video I/O (file and camera based input, image/video file 23

24 矩阵 / 向量数据操作及线性代数运算 ( 矩阵乘积 矩阵方程求解 特征值 奇异值分解 ) Matrix and vector manipulation and linear algebra routines (products, solvers, eigenvalues, SVD). 支持多种动态数据结构 ( 链表 队列 数据集 树 图 ) Various dynamic data structures (lists, queues, sets, trees, graphs). 基本图像处理 ( 去噪 边缘检测 角点检测 采样与插值 色彩变换 形态学处理 直方图 图像金字塔结构 ) Basic image processing (filtering, edge detection, corner detection, sampling and interpolation, color conversion, morphological operations, histograms, image pyramids). 结构分析 ( 连通域 / 分支 轮廓处理 距离转换 图像矩 模板匹配 霍夫变换 多项式逼近 曲线拟合 椭圆拟合 狄劳尼三角化 ) Structural analysis (connected components, contour processing, distance transform, various moments, template matching, Hough transform, polygonal approximation, line fitting, ellipse fitting, Delaunay triangulation). 摄像头定标 ( 寻找和跟踪定标模式 参数定标 基本矩阵估计 单应矩阵估计 立体视觉匹配 ) Camera calibration (finding and tracking calibration patterns, calibration, fundamental matrix estimation, homography estimation, stereo correspondence). 运动分析 ( 光流 动作分割 目标跟踪 ) Motion analysis (optical flow, motion segmentation, tracking). 目标识别 ( 特征方法 HMM 模型 ) Object recognition (eigen-methods, HMM). 基本的 GUI( 显示图像 / 视频 键盘 / 鼠标操作 滑动条 ) scroll-bars). Basic GUI (display image/video, keyboard and mouse handling, 图像标注 ( 直线 曲线 多边形 文本标注 ) Image labeling (line, conic, polygon, text drawing) (3) OpenCV 模块 cv 核心函数库 cvaux 辅助函数库 cxcore 数据结构与线性代数库 highgui GUI 函数库 ml 机器学习函数库 24

25 2 有用的学习资源 (1) 参考手册 : 内 ) <opencv-root>/docs/index.htm ( 译注 : 在你的 OpenCV 安装目录 <opencv-root> (2) 网络资源 : 官方网站 : 软件下载 : (3) 书籍 : Open Source Computer Vision Library by Gary R. Bradski, Vadim Pisarevsky, and Jean-Yves Bouguet, Springer, 1st ed. (June, 2006). chenyusiyuan: 补充以下书籍 Learning OpenCV - Computer Vision with the OpenCV Library by Gary Bradski & Adrian Kaehler, O'Reilly Media, 1 st ed. (September, 2008). OpenCV 教程 基础篇 作者 : 刘瑞祯于仕琪, 北京航空航天大学出版社, 出版日期 : (4) 视频处理例程 ( 在 <opencv-root>/samples/c/): 颜色跟踪 : camshiftdemo 点跟踪 : lkdemo 动作分割 : motempl 边缘检测 : laplace (5) 图像处理例程 ( 在 <opencv-root>/samples/c/): 边缘检测 : edge 图像分割 : pyramid_segmentation 形态学 : morphology 直方图 : demhist 距离变换 : distrans 椭圆拟合 : fitellipse 3 OpenCV 命名规则 25

26 (1) 函数名 : cvactiontargetmod(...) Action = 核心功能 (core functionality) (e.g. set, create) Target = 目标图像区域 (target image area) (e.g. contour, polygon) Mod = ( 可选的 ) 调整语 (optional modifiers) (e.g. argument type) (2) 矩阵数据类型 : CV_<bit_depth>(S U F)C<number_of_channels> S = 符号整型 U = 无符号整型 F = 浮点型 E.g.: CV_8UC1 是指一个 8 位无符号整型单通道矩阵, CV_32FC2 是指一个 32 位浮点型双通道矩阵. (3) 图像数据类型 : IPL_DEPTH_<bit_depth>(S U F) E.g.: IPL_DEPTH_8U 图像像素数据是 8 位无符号整型. IPL_DEPTH_32F 图像像素数据是 32 位浮点型. (4) 头文件 : #include <cv.h> #include <cvaux.h> #include <highgui.h> #include <ml.h> #include <cxcore.h> // 一般不需要,cv.h 内已包含该头文件 4 编译建议 (1) Linux: g++ hello-world.cpp -o hello-world \ -I /usr/local/include/opencv -L /usr/local/lib \ -lm -lcv -lhighgui lcvaux (2) Windows: 在 Visual Studio 的 选项 和 项目 中设置好 OpenCV 相关文件的路径 5 C 例程 ///////////////////////////////////////////////////////////////// /////// // // hello-world.cpp // // 该程序从文件中读入一幅图像, 将之反色, 然后显示出来. // ///////////////////////////////////////////////////////////////// /////// #include <stdlib.h> 26

27 #include <stdio.h> #include <math.h> #include <cv.h> #include <highgui.h> 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 <image-file-name>\n\7"); exit(0); } // load an image img=cvloadimage(argv[1]); if(!img) printf("could not load image file: %s\n",argv[1]); exit(0); } // get the image data 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); // create a window cvnamedwindow("mainwin", CV_WINDOW_AUTOSIZE); cvmovewindow("mainwin", 100, 100); // invert the image // 相当于 cvnot(img); for(i=0;i<height;i++) for(j=0;j<width;j++) for(k=0;k<channels;k++) data[i*step+j*channels+k]=255-data[i*step+j*channels+k]; // show the image 27

28 cvshowimage("mainwin", img ); // wait for a key cvwaitkey(0); } // release the image cvreleaseimage(&img ); return 0; 二 GUI 指令 1 窗口管理 (1) 创建和定位一个新窗口 : cvnamedwindow("win1", CV_WINDOW_AUTOSIZE); cvmovewindow("win1", 100, 100); // offset from the UL corner of the screen (2) 载入图像 : IplImage* img=0; img=cvloadimage(filename); if(!img) printf("could not load image file: %s\n",filename); (3) 显示图像 : cvshowimage("win1",img); 该函数可以显示彩色或灰度的字节型 / 浮点型图像 字节型图像像素值范围为 [0-255]; 浮点 型图像像素值范围为 [0-1] 彩色图像的三色元素按 BGR( 蓝 - 红 - 绿 ) 顺序存储 (4) 关闭窗口 : cvdestroywindow("win1"); (5) 改变窗口大小 : cvresizewindow("win1",100,100); // new width/heigh in pixels 2 输入处理 (1) 处理鼠标事件 : 定义一个鼠标处理程序 : void mousehandler(int event, int x, int y, int flags, void* param) switch(event) case CV_EVENT_LBUTTONDOWN: if(flags & CV_EVENT_FLAG_CTRLKEY) 28

29 printf("left button down with CTRL pressed\n"); break; case CV_EVENT_LBUTTONUP: printf("left button up\n"); break; } } x,y: 相对于左上角的像素坐标 event: CV_EVENT_LBUTTONDOWN, CV_EVENT_RBUTTONDOWN, CV_EVENT_MBUTTONDOWN, CV_EVENT_LBUTTONUP, CV_EVENT_RBUTTONUP, CV_EVENT_MBUTTONUP, CV_EVENT_LBUTTONDBLCLK, CV_EVENT_RBUTTONDBLCLK, CV_EVENT_MBUTTONDBLCLK, CV_EVENT_MOUSEMOVE: flags: CV_EVENT_FLAG_CTRLKEY, CV_EVENT_FLAG_SHIFTKEY, CV_EVENT_FLAG_ALTKEY, CV_EVENT_FLAG_LBUTTON, CV_EVENT_FLAG_RBUTTON, CV_EVENT_FLAG_MBUTTON 注册该事件处理程序 : mouseparam=5; cvsetmousecallback("win1",mousehandler,&mouseparam); (2) 处理键盘事件 : 实际上对于键盘输入并没有专门的事件处理程序. int key; 按一定间隔检测键盘输入 ( 适用于循环体中 ): key=cvwaitkey(10); // wait 10ms for input int key; 中止程序等待键盘输入 : key=cvwaitkey(0); // wait indefinitely for input 键盘输入的循环处理程序 : while(1) key=cvwaitkey(10); if(key==27) break; switch(key) case 'h':... break; case 'i':... break; } 29

30 } (3) 处理滑动条事件 : 定义一个滑动条处理程序 : void trackbarhandler(int pos) printf("trackbar position: %d\n",pos); } 注册该事件处理程序 : int trackbarval=25; int maxval=100; cvcreatetrackbar("bar1", "win1", &trackbarval,maxval, trackbarhandler); 获取当前的滑动条位置 : int pos = cvgettrackbarpos("bar1","win1"); 设置滑动条位置 : cvsettrackbarpos("bar1", "win1", 25); 三 OpenCV 的基本数据结构 ( 译注 :OpenCV 或 2.0 版本中各数据结构的结构体元素有所调整, 以下仅作参考 ) 1 图像数据结构 (1) IPL 图像 : IplImage -- int nchannels; // 颜色通道数目 (1,2,3,4) -- int depth; // 像素的位深 : // IPL_DEPTH_8U, IPL_DEPTH_8S, // IPL_DEPTH_16U,IPL_DEPTH_16S, // IPL_DEPTH_32S,IPL_DEPTH_32F, // IPL_DEPTH_64F -- int width; // 图像宽度 ( 像素为单位 ) -- int height; // 图像高度 -- char* imagedata; // 图像数据指针 // 注意彩色图像按 BGR 顺序存储数据 -- int dataorder; // 0 - 将像素点不同通道的值交错排在一起, 形成单一 像素平面 30

31 // 1 - 把所有像素同通道值排在一起, 形成若干个通道平 面, 再把平面排列起来 // cvcreateimage 只能创建像素交错排列式的图像 -- int origin; // 0 像素原点为左上角, // 1 像素原点为左下角 (Windows bitmaps style) -- int widthstep; // 相邻行的同列点之间的字节数 -- int imagesize; // 图像的大小 ( 字节为单位 ) = height*widthstep -- struct _IplROI *roi;// 图像的感兴趣区域 (ROI). ROI 非空时对图像的 // 处理仅限于 ROI 区域. -- char *imagedataorigin; // 图像数据未对齐时的数据原点指针 // ( 需要正确地重新分配图像内存 ) // (needed for correct image deallocation) -- int align; // 图像数据的行对齐 : 4 or 8 byte alignment // OpenCV 中无此项, 采用 widthstep 代替 -- char colormodel[4]; // 颜色模型 OpenCV 中忽略此项 2 矩阵与向量 (1) 矩阵 : CvMat // 2D 矩阵 -- int type; // 元素类型 (uchar,short,int,float,double) 与标志 -- int step; // 整行长度字节数 -- int rows, cols; // 行 列数 -- int height, width; // 矩阵高度 宽度, 与 rows cols 对应 -- union data; CvMatND -- uchar* ptr; // data pointer for an unsigned char matrix -- short* s; // data pointer for a short matrix -- int* i; // data pointer for an integer matrix -- float* fl; // data pointer for a float matrix -- double* db; // data pointer for a double matrix // N- 维矩阵 -- int type; // 元素类型 (uchar,short,int,float,double) 与标志 -- int dims; // 矩阵维数 -- union data; -- uchar* ptr; // data pointer for an unsigned char matrix -- short* s; // data pointer for a short matrix -- int* i; // data pointer for an integer matrix -- float* fl; // data pointer for a float matrix -- double* db; // data pointer for a double matrix -- struct dim[]; // 各维信息 -- size; // 元素数目 -- step; // 元素间距 ( 字节为单位 ) 31

32 CvSparseMat // N- 维稀疏矩阵 (2) 一般矩阵 : CvArr* // 仅作为函数定义的参数使用, // 表明函数可以接受不同类型的矩阵作为参数, // 例如 :IplImage*, CvMat* 甚至是 CvSeq*. // 矩阵的类型通过矩阵头的前 4 个字节信息来确定 (3) 标量 : CvScalar -- double val[4]; //4D 向量 初始化函数 : CvScalar s = cvscalar(double val0, double val1=0, double val2=0, double val3=0); // Example: CvScalar s = cvscalar(20.0); s.val[0]=20.0; 注意该初始化函数的函数名与对应的结构体名称几乎同名, 差别仅在于函数名第一个字母是 小写的, 而结构体名第一个字母是大写的 它并不是一个 C++ 构造函数 ( 译注 : 类似 的还有 cvmat 与 CvMat cvpoint 与 CvPoint 等等 ) 3 其它结构类型 (1) 点 : CvPoint p = cvpoint(int x, int y); CvPoint2D32f p = cvpoint2d32f(float x, float y); CvPoint3D32f p = cvpoint3d32f(float x, float y, float z); //E.g.: p.x=5.0; p.y=5.0; (2) 矩形框大小 ( 以像素为精度 ): CvSize r = cvsize(int width, int height); CvSize2D32f r = cvsize2d32f(float width, float height); (3) 矩形框的偏置和大小 : CvRect r = cvrect(int x, int y, int width, int height); 四 图像处理 32

33 1 图像的内存分配与释放 (1) 分配内存给一幅新图像 : IplImage* cvcreateimage(cvsize size, int depth, int channels); size: cvsize(width,height); depth: 像素深度 : IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F, IPL_DEPTH_64F channels: 像素通道数. Can be 1, 2, 3 or 4. 各通道是交错排列的. 一幅彩色图像的数据排列格式如下 : b0 g0 r0 b1 g1 r1... 示例 : // Allocate a 1-channel byte image IplImage* img1=cvcreateimage(cvsize(640,480),ipl_depth_8u,1); // Allocate a 3-channel float image IplImage* img2=cvcreateimage(cvsize(640,480),ipl_depth_32f,3); (2) 释放图像 : IplImage* img=cvcreateimage(cvsize(640,480),ipl_depth_8u,1); cvreleaseimage(&img); (3) 复制图像 : IplImage* img1=cvcreateimage(cvsize(640,480),ipl_depth_8u,1); IplImage* img2; img2=cvcloneimage(img1); // 注意通过 cvcloneimage 得到的图像 // 也要用 cvreleaseimage 释放, 否则容易产生内存泄漏 (4) 设置 / 获取感兴趣区域 ROI: void cvsetimageroi(iplimage* image, CvRect rect); void cvresetimageroi(iplimage* image); CvRect cvgetimageroi(const IplImage* image); 大多数 OpenCV 函数都支持 ROI. (5) 设置 / 获取感兴趣通道 COI: void cvsetimagecoi(iplimage* image, int coi); // 0=all int cvgetimagecoi(const IplImage* image); 大多数 OpenCV 函数不支持 COI. 33

34 2 图像读写 (1) 从文件中读入图像 : IplImage* img=0; img=cvloadimage(filename); if(!img) printf("could not load image file: %s\n",filename); 支持的图像格式 : BMP, DIB, JPEG, JPG, JPE, PNG, PBM, PGM, PPM, SR, RAS, TIFF, TIF OpenCV 默认将读入的图像强制转换为一幅三通道彩色图像. 不过可以按以下方法修改读入 方式 : img=cvloadimage(filename,flag); flag: >0 将读入的图像强制转换为一幅三通道彩色图像 =0 将读入的图像强制转换为一幅单通道灰度图像 <0 读入的图像通道数与所读入的文件相同. (2) 保存图像 : if(!cvsaveimage(outfilename,img)) printf("could not save: %s\n", outfilename); 保存的图像格式由 outfilename 中的扩展名确定. 3 访问图像像素 (1) 假设你要访问第 k 通道 第 i 行 第 j 列的像素 (2) 间接访问 : ( 通用, 但效率低, 可访问任意格式的图像 ) 对于单通道字节型图像 : 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 对于多通道字节型 / 浮点型图像 : 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; 34

35 s.val[2]=111; cvset2d(img,i,j,s); // set the (i,j) pixel value (3) 直接访问 : ( 效率高, 但容易出错 ) 对于单通道字节型图像 : 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 (4) 基于指针的直接访问 : ( 简单高效 ) 对于单通道字节型图像 : 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; 35

36 data[i*step+j*channels+k] = 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/sizeof(float); int channels = img->nchannels; float * data = (float *)img->imagedata; data[i*step+j*channels+k] = 111; (5) 基于 c++ wrapper 的直接访问 : ( 更简单高效 ) 首先定义一个 c++ wrapper Image, 然后基于 Image 定义不同类型的图像 : template<class T> 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<RgbPixel> RgbImage; typedef Image<RgbPixelFloat> RgbImageFloat; typedef Image<unsigned char> BwImage; typedef Image<float> BwImageFloat; 对于单通道字节型图像 : IplImage* img=cvcreateimage(cvsize(640,480),ipl_depth_8u,1); BwImage imga(img); imga[i][j] = 111; 36

37 对于多通道字节型图像 : 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; 4 图像转换 (1) 字节型图像的灰度 - 彩色转换 : cvconvertimage(src, dst, flags=0); src = float/byte grayscale/color image dst = byte grayscale/color image flags = CV_CVTIMG_FLIP ( 垂直翻转图像 ) CV_CVTIMG_SWAP_RB ( 置换 R 和 B 通道 ) (2) 彩色图像 -> 灰度图像 : // Using the OpenCV conversion: cvcvtcolor(cimg,gimg,cv_bgr2gray); // cimg -> gimg // Using a direct conversion: for(i=0;i<cimg->height;i++) for(j=0;j<cimg->width;j++) gimga[i][j]= (uchar)(cimga[i][j].b* cimga[i][j].g* cimga[i][j].r*0.299); (3) 不同彩色空间之间的转换 : cvcvtcolor(src,dst,code); // src -> dst code = CV_<X>2<Y> <X>/<Y> = RGB, BGR, GRAY, HSV, YCrCb, XYZ, Lab, Luv, HLS e.g.: CV_BGR2GRAY, CV_BGR2HSV, CV_BGR2Lab 5 绘图指令 37

38 (1) 绘制矩形 : // 在点 (100,100) 和 (200,200) 之间绘制一矩形, 边线用红色 宽度为 1 cvrectangle(img, cvpoint(100,100), cvpoint(200,200), cvscalar(255,0,0), 1); (2) 绘制圆形 : // 圆心为 (100,100) 半径为 20. 圆周绿色 宽度为 1 cvcircle(img, cvpoint(100,100), 20, cvscalar(0,255,0), 1); (3) 绘制线段 : // 在 (100,100) 和 (200,200) 之间 线宽为 1 的绿色线段 cvline(img, cvpoint(100,100), cvpoint(200,200), cvscalar(0,255,0), 1); (4) 绘制一组线段 : 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 int int int ncurvepts[2]=4,5}; ncurves=2; iscurveclosed=1; linewidth=1; cvpolyline(img,curvearr,ncurvepts,ncurves,iscurveclosed,cvscalar( 0,255,255),lineWidth); void cvpolyline( CvArr* img, CvPoint** pts, int* npts, int contours, int is_closed, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像 pts 折线的顶点指针数组 npts 折线的定点个数数组 也可以认为是 pts 指针数组的大小 contours 折线的线段数量 is_closed 指出多边形是否封闭 如果封闭, 函数将起始点和结束点连线 color 折线的颜色 thickness 线条的粗细程度 line_type 线段的类型 参见 cvline shift 顶点的小数点位数 (5) 绘制一组填充颜色的多边形 : cvfillpoly(img,curvearr,ncurvepts,ncurves,cvscalar(0,255,255)); 38

39 cvfillpoly 用于一个单独被多边形轮廓所限定的区域内进行填充 函数可以填充复杂的 区域, 例如, 有漏洞的区域和有交叉点的区域等等 void cvfillpoly( CvArr* img, CvPoint** pts, int* npts, int contours,cvscalar color, int line_type=8, int shift=0 ); img pts npts contours color 图像 指向多边形的数组指针 多边形的顶点个数的数组 组成填充区域的线段的数量 多边形的颜色 line_type 组成多边形的线条的类型 shift 顶点坐标的小数点位数 (6) 文本标注 : 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)); 其它可用的字体类型有 : CV_FONT_HERSHEY_SIMPLEX, CV_FONT_HERSHEY_PLAIN, CV_FONT_HERSHEY_DUPLEX, CV_FONT_HERSHEY_COMPLEX, CV_FONT_HERSHEY_TRIPLEX, CV_FONT_HERSHEY_COMPLEX_SMALL, CV_FONT_HERSHEY_SCRIPT_SIMPLEX, CV_FONT_HERSHEY_SCRIPT_COMPLEX, 五 矩阵处理 1 矩阵的内存分配与释放 (1) 总体上 : OpenCV 使用 C 语言来进行矩阵操作 不过实际上有很多 C++ 语言的替代方案可以更高效地完成 在 OpenCV 中向量被当做是有一个维数为 1 的 N 维矩阵. 矩阵按行 - 行方式存储, 每行以 4 字节 (32 位 ) 对齐. (2) 为新矩阵分配内存 : CvMat* cvcreatemat(int rows, int cols, int type); 39

40 type: 矩阵元素类型. 按 CV_<bit_depth>(S U F)C<number_of_channels> 方式指定. 例如 : CV_8UC1 CV_32SC2. 示例 : 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); // does not seem to be working properly 2 访问矩阵元素 (1) 假设需要访问一个 2D 浮点型矩阵的第 (i, j) 个单元. (2) 间接访问 : cvmset(m,i,j,2.0); // Set M(i,j) t = cvmget(m,i,j); // Get M(i,j) (3) 直接访问 ( 假设矩阵数据按 4 字节行对齐 ): CvMat* M = cvcreatemat(4,4,cv_32fc1); int n = M->cols; float *data = M->data.fl; data[i*n+j] = 3.0; 40

41 (4) 直接访问 ( 当数据的行对齐可能存在间隙时 possible alignment gaps): CvMat* M = cvcreatemat(4,4,cv_32fc1); int step = M->step/sizeof(float); float *data = M->data.fl; (data+i*step)[j] = 3.0; (5) 对于初始化后的矩阵进行直接访问 : double a[16]; CvMat Ma = cvmat(3, 4, CV_64FC1, a); a[i*4+j] = 2.0; // Ma(i,j)=2.0; 3 矩阵 / 向量运算 (1) 矩阵之间的运算 : 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 (2) 矩阵之间的元素级运算 : CvMat *Ma, *Mb, *Mc; cvmul(ma, Mb, Mc); cvdiv(ma, Mb, Mc); // Ma.*Mb -> Mc // 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); // 向量点乘 : Va. Vb -> res cvcrossproduct(&va, &Vb, &Vc); // 向量叉乘 : Va x Vb -> Vc 注意在进行叉乘运算时,Va, Vb, Vc 必须是仅有 3 个元素的向量. (4) 单一矩阵的运算 : CvMat *Ma, *Mb; cvtranspose(ma, Mb); 返回给 Ma 本身 ) // 转置 :transpose(ma) -> Mb ( 注意转置阵不能 41

42 CvScalar t = cvtrace(ma); // 迹 :trace(ma) -> t.val[0] double d = cvdet(ma); // 行列式 :det(ma) -> d 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* l = 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 = U D V^T 标志位使矩阵 U 或 V 按转置形式返回 ( 若不转置可能运算出错 ). 六 视频处理 1 从视频流中捕捉一帧画面 (1) OpenCV 支持从摄像头或视频文件 (AVI 格式 ) 中捕捉帧画面. (2) 初始化一个摄像头捕捉器 : CvCapture* capture = cvcapturefromcam(0); // capture from video device #0 (3) 初始化一个视频文件捕捉器 : CvCapture* capture = cvcapturefromavi("infile.avi"); (4) 捕捉一帧画面 : IplImage* img = 0; if(!cvgrabframe(capture)) printf("could not grab a frame\n\7"); exit(0); 42 // capture a frame

43 } img=cvretrieveframe(capture); // retrieve the captured frame 若要从多个摄像头中同步捕捉画面, 则须首先从每个摄像头中抓取一帧, 紧接着要将被捕捉 的帧画面恢复到一个 IplImage* 型图像中 ( 译注 : 这一过程其实可以用 cvqueryframe() 函 数一步完成 ) (5) 释放视频流捕捉器 : cvreleasecapture(&capture); 注意由视频流捕捉器得到的图像是由捕捉器分配和释放内存的, 不需要单独对图像进行释放 内存的操作 2 获取 / 设置视频流信息 (1) 获取视频流设备信息 : cvqueryframe(capture); // 在读取视频流信息前, 要先执行此操作 int frameh = (int) cvgetcaptureproperty(capture, CV_CAP_PROP_FRAME_HEIGHT); int framew CV_CAP_PROP_FRAME_WIDTH); int fps = (int) cvgetcaptureproperty(capture, = (int) cvgetcaptureproperty(capture, CV_CAP_PROP_FPS); int numframes = (int) cvgetcaptureproperty(capture, CV_CAP_PROP_FRAME_COUNT); 统计总帧数仅对视频文件有效, 但似乎不太准确 ( 译注 : 也许 OpenCV2.0 中此问题已解决 ) (2) 获取帧图信息 : float posmsec = cvgetcaptureproperty(capture, CV_CAP_PROP_POS_MSEC); int posframes CV_CAP_PROP_POS_FRAMES); float posratio = CV_CAP_PROP_POS_AVI_RATIO); = (int) cvgetcaptureproperty(capture, cvgetcaptureproperty(capture, 所抓取的帧的位置有三种表达方式 : 距离第一帧画面的时间间隔 ( 毫秒为单位 ), 或者距离 第一帧画面 ( 序列号为 0) 的序列数. 第三种方式是按相对比率, 第一帧的相对比率为 0, 最后一帧的相对比率为 1. 此方式仅对读取视频文件时有效. (3) 设置从视频文件抓取的第一帧画面的位置 : // start capturing from a relative position of 0.9 of a video file cvsetcaptureproperty(capture, CV_CAP_PROP_POS_AVI_RATIO, (double)0.9); 注意此方法定位并不准确 43

44 3 保存视频文件 (1) 初始化视频编写器 : 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, 则 运行时会弹出一个编码器选择框. (2) 保持视频文件 : IplImage* img = 0; int nframes = 50; for(i=0;i<nframes;i++) } cvgrabframe(capture); // capture a frame img=cvretrieveframe(capture); // retrieve the captured frame // img = cvqueryframe(capture); cvwriteframe(writer,img); 要查看所抓取到的帧画面, 可以在循环中加入以下语句 : cvshowimage("mainwin", img); key=cvwaitkey(20); // add the frame to the file // wait 20 ms 注意 cvwaitkey 参数应该不小于 20 ms, 否则画面的显示可能出错. (3) 释放视频编写器 : cvreleasevideowriter(&writer); 44

45 OpenCV 概述 Wikipedia, 自由的百科全书 Intel 开源计算机视觉库 OpenCV 什么是 OpenCV OpenCV 是 Intel 开源计算机视觉库 它由一系列 C 函数和少量 C++ 类构成, 实现了图像 处理和计算机视觉方面的很多通用算法 重要特性 OpenCV 拥有包括 300 多个 C 函数的跨平台的中 高层 API 它不依赖于其它的外部库 尽管也可以使用某些外部库 OpenCV 对非商业应用和商业应用都是免费 (FREE) 的 ( 细节参考 BSD License) OpenCV 为 Intel Integrated Performance Primitives (IPP) 提供了透明接口 这意味着如果有为特定处理器优化的的 IPP 库, OpenCV 将在运行时自动加载这些库 更多关于 IPP 的信息请参考 : 谁创建了它作者列表可以在文件 AUTHORS 中找到 此外, 还有很多人给出了建议 补丁 BUG 报告等等 一个不太完整的列表在文件 THANKS 中 新特征 请参考 OpenCVChangeLog 从哪里下载 OpenCV 访问 您可以直接下载 exe 文件的安装包 (windows 用户 ), 或者您可以通过代码管理工具 ( 比如 SVN 或者 CVS 等下载 opencv 最新的源代码 ), 您甚至不需要安装任何代码管理工具, 直接通过 sourceforge 网站上提供的打包下载源代码的办法下载, 具体可以参考 Mingw 编译最新版本的 OpenCV 代码 如果在使用 OpenCV 存在问题, 在 Google ( ) 中输入 "OpenCV" 和相关问题的关键字进行搜索 也可以到本网站的论坛上面发帖来咨询 论坛地址是 :opencv 中文论坛 如果在安装 / 运行 / 使用 OpenCV 中遇到问题 1. 阅读 FAQ 中文 45

46 2. 在 OpenCV 邮件列表 ( ) 中搜索 3. 加入到 yahoo group 上的 OpenCV 邮件列表中 ( 如何加入请参考 FAQs), 并发送你的问题到邮件列表中 ( 这个邮件列表可能会迁移到 OpenCV's SourceForge site) 4. 参考 OpenCV 的例子代码, 阅读参考手册 :) OpenCV 参考手册 CxCore 中文参考手册 Cv 中文参考手册 CvAux 中文参考手册 HighGUI 中文参考手册 FAQ 中文 1. 安装配置问题 1.1 缺少 highgui100.dll 是由于 highgui100.dll 所在目录 ( 一般为 C:\Program Files\OpenCV\bin) 没有添加到系统环 境变量所致, 请参考 VC6 下安装与配置 # 配置 Windows 环境变量 2. 使用库的技术问题 2.1 视频读写出现问题 请参考 : 视频读写概述 2.2 怎么访问图像像素 ( 坐标是从 0 开始的, 并且是相对图像原点的位置 图像原点或者是左上角 (img->origin=ipl_origin_tl) 或者是左下角 (img->origin=ipl_origin_bl) ) 假设有 8-bit 1- 通道的图像 I (IplImage* img): I(x,y) ~ ((uchar*)(img->imagedata + img->widthstep*y))[x] 假设有 8-bit 3- 通道的图像 I (IplImage* img): I(x,y)blue ~ ((uchar*)(img->imagedata + img->widthstep*y))[x*3] 46

47 I(x,y)green ~ ((uchar*)(img->imagedata + img->widthstep*y))[x*3+1] I(x,y)red ~ ((uchar*)(img->imagedata + img->widthstep*y))[x*3+2] 例如, 给点 (100,100) 的亮度增加 30, 那么可以这样做 : CvPoint pt = 100,100}; ((uchar*)(img->imagedata + img->widthstep*pt.y))[pt.x*3] += 30; ((uchar*)(img->imagedata + img->widthstep*pt.y))[pt.x*3+1] += 30; ((uchar*)(img->imagedata + img->widthstep*pt.y))[pt.x*3+2] += 30; 或者更高效地 : CvPoint pt = 100,100}; uchar* temp_ptr = &((uchar*)(img->imagedata + img->widthstep*pt.y))[pt.x*3]; temp_ptr[0] += 30; temp_ptr[1] += 30; temp_ptr[2] += 30; 假设有 32-bit 浮点数, 1- 通道图像 I (IplImage* img): I(x,y) ~ ((float*)(img->imagedata + img->widthstep*y))[x] 现在, 一般的情况下, 假设有 N- 通道, 类型为 T 的图像 : I(x,y)c ~ ((T*)(img->imageData + img->widthstep*y))[x*n + c] 你可以使用宏 CV_IMAGE_ELEM( image_header, elemtype, y, x_nc ) I(x,y)c ~ CV_IMAGE_ELEM( img, T, y, x*n + c ) 也有针对各种图像 ( 包括 4 通道图像 ) 和矩阵的函数 (cvget2d, cvset2d), 但是它们非常慢 2.3 如何访问矩阵元素? 方法是类似的 ( 下面的例子都是针对 0 起点的列和行 ) 设有 32-bit 浮点数的实数矩阵 M (CvMat* mat): M(i,j) ~ ((float*)(mat->data.ptr + mat->step*i))[j] 设有 64-bit 浮点数的复数矩阵 M (CvMat* mat): Re M(i,j) ~ ((double*)(mat->data.ptr + mat->step*i))[j*2] Im M(i,j) ~ ((double*)(mat->data.ptr + mat->step*i))[j*2+1] 对单通道矩阵, 有宏 CV_MAT_ELEM( matrix, elemtype, row, col ), 例如对 32-bit 浮点数的实数矩阵 : M(i,j) ~ CV_MAT_ELEM( mat, float, i, j ), 例如, 这儿是一个 3x3 单位矩阵的初始化 : CV_MAT_ELEM( mat, float, 0, 0 ) = 1.f; CV_MAT_ELEM( mat, float, 0, 1 ) = 0.f; CV_MAT_ELEM( mat, float, 0, 2 ) = 0.f; CV_MAT_ELEM( mat, float, 1, 0 ) = 0.f; 47

48 CV_MAT_ELEM( mat, float, 1, 1 ) = 1.f; CV_MAT_ELEM( mat, float, 1, 2 ) = 0.f; CV_MAT_ELEM( mat, float, 2, 0 ) = 0.f; CV_MAT_ELEM( mat, float, 2, 1 ) = 0.f; CV_MAT_ELEM( mat, float, 2, 2 ) = 1.f; 2.4 如何在 OpenCV 中处理我自己的数据 设你有 300x bit 浮点数 image/array, 也就是对一个有 个元素的数组 int cols = 300, rows = 200; float* myarr = new float[rows*cols]; // 第一步, 初始化 CvMat 头 CvMat mat = cvmat( rows, cols, CV_32FC1, // 32 位浮点单通道类型 myarr // 用户数据指针 ( 数据没有被复制 ) ); // 第二步, 使用 cv 函数, 例如计算 l2 (Frobenius) 模 double norm = cvnorm( &mat, 0, CV_L2 );... delete myarr; 其它情况在参考手册中有描述 见 cvcreatematheader,cvinitmatheader, cvcreateimageheader, cvsetdata 等 2.5 如何读入和显示图像 /* usage: prog <image_name> */ #include "cv.h" #include "highgui.h" int main( int argc, char** argv ) IplImage* img; if( argc == 2 && (img = cvloadimage( argv[1], 1))!= 0 ) cvnamedwindow( "Image view", 1 ); cvshowimage( "Image view", img ); cvwaitkey(0); // 非常重要, 内部包含事件处理循环 cvdestroywindow( "Image view" ); cvreleaseimage( &img ); return 0; } return -1; } 48

49 2.6 如何检测和处理轮廓线 参考 squares demo /* 在程序里找寻矩形 */ #ifdef _CH_ #pragma package <opencv> #endif #ifndef _EiC #include "cv.h" #include "highgui.h" #include <stdio.h> #include <math.h> #include <string.h> #endif int thresh = 50; IplImage* img = 0; IplImage* img0 = 0; CvMemStorage* storage = 0; CvPoint pt[4]; const char* wndname = "Square Detection Demo"; // helper function: // finds a cosine of angle between vectors // from pt0->pt1 and from pt0->pt2 double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 ) double dx1 = pt1->x - pt0->x; double dy1 = pt1->y - pt0->y; double dx2 = pt2->x - pt0->x; double dy2 = pt2->y - pt0->y; return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); } // returns sequence of squares detected on the image. // the sequence is stored in the specified memory storage CvSeq* findsquares4( IplImage* img, CvMemStorage* storage ) 49

50 CvSeq* contours; int i, c, l, N = 11; CvSize sz = cvsize( img->width & -2, img->height & -2 ); IplImage* timg = cvcloneimage( img ); // make a copy of input image IplImage* gray = cvcreateimage( sz, 8, 1 ); IplImage* pyr = cvcreateimage( cvsize(sz.width/2, sz.height/2), 8, 3 ); IplImage* tgray; CvSeq* result; double s, t; // create empty sequence that will contain points - // 4 points per square (the square's vertices) CvSeq* squares = cvcreateseq( 0, sizeof(cvseq), sizeof(cvpoint), storage ); // select the maximum ROI in the image // with the width and height divisible by 2 cvsetimageroi( timg, cvrect( 0, 0, sz.width, sz.height )); // down-scale and upscale the image to filter out the noise cvpyrdown( timg, pyr, 7 ); cvpyrup( pyr, timg, 7 ); tgray = cvcreateimage( sz, 8, 1 ); // find squares in every color plane of the image for( c = 0; c < 3; c++ ) // extract the c-th color plane cvsetimagecoi( timg, c+1 ); cvcopy( timg, tgray, 0 ); // try several threshold levels for( l = 0; l < N; l++ ) // hack: use Canny instead of zero threshold level. // Canny helps to catch squares with gradient shading if( l == 0 ) // apply Canny. Take the upper threshold from slider // and set the lower to 0 (which forces edges merging) cvcanny( tgray, gray, 0, thresh, 5 ); // dilate canny output to remove potential // holes between edge segments cvdilate( gray, gray, 0, 1 ); } else 50

51 } // apply threshold if l!=0: // tgray(x,y) = gray(x,y) < (l+1)*255/n? 255 : 0 cvthreshold( tgray, gray, (l+1)*255/n, 255, CV_THRESH_BINARY ); // find contours and store them all as a list cvfindcontours( gray, storage, &contours, sizeof(cvcontour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvpoint(0,0) ); // test each contour while( contours ) // approximate contour with accuracy proportional // to the contour perimeter result = cvapproxpoly( contours, sizeof(cvcontour), storage, CV_POLY_APPROX_DP, cvcontourperimeter(contours)*0.02, 0 ); // square contours should have 4 vertices after approximation // relatively large area (to filter out noisy contours) // and be convex. // Note: absolute value of an area is used because // area may be positive or negative - in accordance with the // contour orientation if( result->total == 4 && fabs(cvcontourarea(result,cv_whole_seq)) > 1000 && cvcheckcontourconvexity(result) ) s = 0; for( i = 0; i < 5; i++ ) // find minimum angle between joint // edges (maximum of cosine) if( i >= 2 ) t = fabs(angle( (CvPoint*)cvGetSeqElem( result, i ), (CvPoint*)cvGetSeqElem( result, i-2 ), (CvPoint*)cvGetSeqElem( result, i-1 ))); s = s > t? s : t; } } // if cosines of all angles are small 51

52 } // (all angles are ~90 degree) then write quandrange // vertices to resultant sequence if( s < 0.3 ) for( i = 0; i < 4; i++ ) cvseqpush( squares, (CvPoint*)cvGetSeqElem( result, i )); } } } // take the next contour contours = contours->h_next; // release all the temporary images cvreleaseimage( &gray ); cvreleaseimage( &pyr ); cvreleaseimage( &tgray ); cvreleaseimage( &timg ); } return squares; // the function draws all the squares in the image void drawsquares( IplImage* img, CvSeq* squares ) CvSeqReader reader; IplImage* cpy = cvcloneimage( img ); int i; // initialize reader of the sequence cvstartreadseq( squares, &reader, 0 ); // read 4 sequence elements at a time (all vertices of a square) for( i = 0; i < squares->total; i += 4 ) CvPoint* rect = pt; int count = 4; // read 4 vertices memcpy( pt, reader.ptr, squares->elem_size ); CV_NEXT_SEQ_ELEM( squares->elem_size, reader ); memcpy( pt + 1, reader.ptr, squares->elem_size ); 52

53 CV_NEXT_SEQ_ELEM( squares->elem_size, reader ); memcpy( pt + 2, reader.ptr, squares->elem_size ); CV_NEXT_SEQ_ELEM( squares->elem_size, reader ); memcpy( pt + 3, reader.ptr, squares->elem_size ); CV_NEXT_SEQ_ELEM( squares->elem_size, reader ); } // draw the square as a closed polyline cvpolyline( cpy, &rect, &count, 1, 1, CV_RGB(0,255,0), 3, CV_AA, 0 ); } // show the resultant image cvshowimage( wndname, cpy ); cvreleaseimage( &cpy ); void on_trackbar( int a ) if( img ) drawsquares( img, findsquares4( img, storage ) ); } char* names[] = "pic1.png", "pic2.png", "pic3.png", "pic4.png", "pic5.png", "pic6.png", 0 }; int main(int argc, char** argv) int i, c; // create memory storage that will contain all the dynamic data storage = cvcreatememstorage(0); for( i = 0; names[i]!= 0; i++ ) // load i-th image img0 = cvloadimage( names[i], 1 ); if(!img0 ) printf("couldn't load %s\n", names[i] ); continue; } img = cvcloneimage( img0 ); // create window and a trackbar (slider) with parent "image" and set callback // (the slider regulates upper threshold, passed to Canny edge detector) 53

54 cvnamedwindow( wndname, 1 ); cvcreatetrackbar( "canny thresh", wndname, &thresh, 1000, on_trackbar ); } // force the image processing on_trackbar(0); // wait for key. // Also the function cvwaitkey takes care of event processing c = cvwaitkey(0); // release both images cvreleaseimage( &img ); cvreleaseimage( &img0 ); // clear memory storage - reset free space position cvclearmemstorage( storage ); if( c == 27 ) break; cvdestroywindow( wndname ); } return 0; #ifdef _EiC main(1,"squares.c"); #endif 2.7 如何用 OpenCV 来定标摄像机 可以使用 \OpenCV\samples\c 目录下的 calibration.cpp 这个程序, 程序的输入支持 USB 摄 像机,avi 文件或者图片 1 使用说明 a. 输入为图片时 : // example command line (for copy-n-paste): // calibration -w 6 -h 8 -s 2 -n 10 -o camera.yml -op -oe [<list_of_views.txt>] /* The list of views may look as following (discard the starting and ending separators): view000.png view001.png #view002.png view003.png 54

55 view010.png one_extra_view.jpg that is, the file will contain 6 lines, view002.png will not be used for calibration, other ones will be (those, in which the chessboard pattern will be found) b. 输入为摄像机或者 avi 文件时 "When the live video from camera is used as input, the following hot-keys may be used:\n" " <ESC>, 'q' - quit the program\n" " 'g' - start capturing images\n" " 'u' - switch undistortion on/off\n"; c 输入参数说明 "Usage: calibration\n" " -w <board_width> # the number of inner corners per one of board dimension\n" " -h <board_height> # the number of inner corners per another board dimension\n" " [-n <number_of_frames>] # the number of frames to use for calibration\n" " # (if not specified, it will be set to the number\n" " # of board views actually available)\n" " [-d <delay>] # a minimum delay in ms between subsequent attempts to capture a next view\n" " # (used only for video capturing)\n" " [-s <square_size>] # square size in some user-defined units (1 by default)\n" " [-o <out_camera_params>] # the output filename for intrinsic [and extrinsic] parameters\n" 55

56 " [-op] # write detected feature points\n" " [-oe] # write extrinsic parameters\n" " [-zt] # assume zero tangential distortion\n" " [-a <aspect_ratio>] # fix aspect ratio (fx/fy)\n" " [-p] # fix the principal point at the center\n" " [-v] # flip the captured images around the horizontal axis\n" " [input_data] # input data, one of the following:\n" " # - text file with a list of the images of the board\n" " # - name of video file with a video of the board\n" " # if input_data not specified, a live view from the camera is used\n" 56

57 CxCore 库的框架概述 为使得 OpenCV 的整个库便于管理和扩充, 将整个库分成若干子库,CxCore 是最重要的一个子库, 从 core 核心 " 名字可以看出, 该库提供了所有 OpenCV 运行时的一些最基本的数据结构, 包括矩阵, 数组的基本运算, 包括出错处理的一些基本函数 具体分为下面若干部分 1.Cxcore 基础结构 CvPoint 二维坐标系下的点, 类型为整型 typedef struct CvPoint int x; /* X 坐标, 通常以 0 为基点 */ int y; /* y 坐标, 通常以 0 为基点 */ } CvPoint; /* 构造函数 */ inline CvPoint cvpoint( int x, int y ); /* 从 CvPoint2D32f 类型转换得来 */ inline CvPoint cvpointfrom32f( CvPoint2D32f point ) ddd CvPoint2D32f 二维坐标下的点, 类型为浮点 typedef struct CvPoint2D32f float x; /* X 坐标, 通常以 0 为基点 */ float y; /* Y 坐标, 通常以 0 为基点 */ } CvPoint2D32f; /* 构造函数 */ inline CvPoint2D32f cvpoint2d32f( double x, double y ); 57

58 /* 从 CvPoint 转换来 */ inline CvPoint2D32f cvpointto32f( CvPoint point ); CvPoint3D32f 三维坐标下的点, 类型为浮点 typedef struct CvPoint3D32f float x; /* x- 坐标, 通常基于 0 */ float y; /* y- 坐标, 通常基于 0 */ float z; /* z- 坐标, 通常基于 0 */ } CvPoint3D32f; /* 构造函数 */ inline CvPoint3D32f cvpoint3d32f( double x, double y, double z ); CvSize 矩形框大小, 以像素为精度 typedef struct CvSize int width; /* 矩形宽 */ int height; /* 矩形高 */ } CvSize; /* 构造函数 */ inline CvSize cvsize( int width, int height ); 注意 : 构造函数的 cv 是小写! CvSize2D32f 以亚像素精度标量矩形框大小 typedef struct CvSize2D32f float width; /* 矩形宽 */ float height; /* 矩形高 */ 58

59 } CvSize2D32f; /* 构造函数 */ inline CvSize2D32f cvsize2d32f( double width, double height ); CvSize2D32f s; s.width = (float)width; s.height = (float)height; } return s; CvRect 矩形框的偏移和大小 typedef struct CvRect int x; /* 方形的最左角的 x- 坐标 */ int y; /* 方形的最上或者最下角的 y- 坐标 */ int width; /* 宽 */ int height; /* 高 */ } CvRect; /* 构造函数 */ inline CvRect cvrect( int x, int y, int width, int height ); CvRect os; os.x = x; os.y = y; os.width = width; os.height = heigth; reture os;} CvScalar 可存放在 1-,2-,3-,4-TUPLE 类型的捆绑数据的容器 typedef struct CvScalar 59

60 double val[4] } CvScalar; /* 构造函数 : 用 val0 初始化 val[0] 用 val1 初始化 val[1], 以此类推 */ inline CvScalar cvscalar( double val0, double val1, double val2, double val3); CvScalar arr; arr.val[4] = val0,val1,val2,val3}; reture arr;} /* 构造函数 : 用 val0123 初始化所有 val[0]...val[3] */ inline CvScalar cvscalarall( double val0123 ); CvScalar arr; arr.val[4] = val0123,val0123,val0123,val0123,}; reture arr;} /* 构造函数 : 用 val0 初始化 val[0], 用 0 初始化 val[1],val[2],val[3] */ inline CvScalar cvrealscalar( double val0 ); CvScalar arr; arr.val[4] = val0}; reture arr;} CvTermCriteria 迭代算法的终止准则 #define CV_TERMCRIT_ITER 1 #define CV_TERMCRIT_NUMBER CV_TERMCRIT_ITER #define CV_TERMCRIT_EPS 2 typedef struct CvTermCriteria 60

61 int type; /* CV_TERMCRIT_ITER 和 CV_TERMCRIT_EPS 二值之一, 或者二者的组合 */ int max_iter; /* 最大迭代次数 */ double epsilon; /* 结果的精确性 */ } CvTermCriteria; /* 构造函数 */ inline CvTermCriteria cvtermcriteria( int type, int max_iter, double epsilon ); /* 在满足 max_iter 和 epsilon 的条件下检查终止准则并将其转换使得 type=cv_termcrit_iter+cv_termcrit_eps */ CvTermCriteria cvchecktermcriteria( CvTermCriteria criteria, double default_eps, int default_max_iters ); CvMat 多通道矩阵 typedef struct CvMat int type; /* CvMat 标识 (CV_MAT_MAGIC_VAL), 元素类型和标记 */ int step; /* 以字节为单位的行数据长度 */ int* refcount; /* 数据引用计数 */ union uchar* ptr; short* s; int* i; float* fl; double* db; } data; /* data 指针 */ #ifdef cplusplus union int rows; int height; }; union int cols; int width; 61

62 }; #else int rows; /* 行数 */ int cols; /* 列数 */ #endif } CvMat; CvMatND 多维 多通道密集数组 typedef struct CvMatND int type; /* CvMatND 标识 (CV_MATND_MAGIC_VAL), 元素类型和标号 */ int dims; /* 数组维数 */ int* refcount; /* 数据参考计数 */ union uchar* ptr; short* s; int* i; float* fl; double* db; } data; /* data 指针 */ /* 每维的数据结构 ( 元素号, 以字节为单位的元素之间的距离 ) 是配套定义的 */ struct int size; int step; } dim[cv_max_dim]; } CvMatND; CvSparseMat 多维 多通道稀疏数组 62

63 typedef struct CvSparseMat int type; /* CvSparseMat 标识 (CV_SPARSE_MAT_MAGIC_VAL), 元素类型和标号 */ int dims; /* 维数 */ int* refcount; /* 参考数量 - 未用 */ struct CvSet* heap; /* HASH 表节点池 */ void** hashtable; /* HASH 表 : 每个入口有一个节点列表, 有相同的 " 以 HASH 大小为模板的 HASH 值 " */ int hashsize; /* HASH 表大小 */ int total; /* 稀疏数组的节点数 */ int valoffset; /* 数组节点值在字节中的偏移 */ int idxoffset; /* 数组节点索引在字节中的偏移 */ int size[cv_max_dim]; /* 维大小 */ } CvSparseMat; IplImage IPL 图像头 typedef struct _IplImage int nsize; /* IplImage 大小,=sizeof(IplImage)*/ int ID; /* 版本 (=0)*/ int nchannels; /* 大多数 OPENCV 函数支持 1,2,3 或 4 个通道 */ int alphachannel; /* 被 OpenCV 忽略 */ int depth; /* 像素的位深度 : IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */ char colormodel[4]; /* 被 OpenCV 忽略 */ char channelseq[4]; /* 被 OpenCV 忽略 */ int dataorder; /* 0 - 交叉存取颜色通道, 对三通道 RGB 图像, 像素存储顺序为 BGR BGR BGR... BGR; 1 - 分开的颜色通道, 对三通道 RGB 图像, 像素存储顺序为 RRR...R GGG...G BBB...B cvcreateimage 只能创建交叉存取图像 */ int origin; /* 0 - 顶 左结构, 1 - 底 左结构 (Windows bitmaps 风格 ) */ int align; /* 图像行排列 (4 or 8). OpenCV 忽略它, 使用 widthstep 代替 */ 63

64 int width; /* 图像宽像素数 */ int height; /* 图像高像素数 */ struct _IplROI *roi;/* 图像感兴趣区域. 当该值非空只对该区域进行处理 */ struct _IplImage *maskroi; /* 在 OpenCV 中必须置 NULL */ void *imageid; /* 同上 */ struct _IplTileInfo *tileinfo; /* 同上 */ int imagesize; /* 图像数据大小 ( 在交叉存取格式下 imagesize=image->height*image->widthstep), 单位字节 */ char *imagedata; /* 指向排列的图像数据 */ int widthstep; /* 排列的图像行大小, 以字节为单位 */ int BorderMode[4]; /* 边际结束模式, 被 OpenCV 忽略 */ int BorderConst[4]; /* 同上 */ char *imagedataorigin; /* 指针指向一个不同的图像数据结构 ( 不是必须排列的 ), 是为了纠正图像内存分配准备的 */ } IplImage; IplImage 结构来自于 Intel Image Processing Library( 是其本身所具有的 ) OpenCV 只支持其中的一个子集 : alphachannel 在 OpenCV 中被忽略 colormodel 和 channelseq 被 OpenCV 忽略 OpenCV 颜色转换的唯一函数 cvcvtcolor 把原图像的颜色空间的目标图像的颜色空间作为一个参数 dataorder 必须是 IPL_DATA_ORDER_PIXEL ( 颜色通道是交叉存取 ), 然而平面图像的被选择通道可以被处理, 就像 COI( 感兴趣的通道 ) 被设置过一样 align 是被 OpenCV 忽略的, 而用 widthstep 去访问后继的图像行 不支持 maskroi 处理 MASK 的函数把他当作一个分离的参数 MASK 在 OpenCV 里是 8-bit, 然而在 IPL 他是 1-bit tileinfo 不支持 BorderMode 和 BorderConst 是不支持的 每个 OpenCV 函数处理像素的邻近的像素, 通常使用单一的固定代码边际模式 除了上述限制,OpenCV 处理 ROI 有不同的要求 要求原图像和目标图像的尺寸或 ROI 的尺寸必须 ( 根据不同的操作, 例如 cvpyrdown 目标图像的宽 ( 高 ) 必须等于原图像的宽 ( 高 ) 除以 2 ±1) 精确匹配, 而 IPL 处理交叉区域, 如图像的大小或 ROI 大小可能是完全独立的 CvArr 不确定数组 typedef void CvArr; 64

65 CvArr* 仅仅是被用于作函数的参数, 用于指示函数接收的数组类型可以不止一个, 如 IplImage*, CvMat* 甚至 CvSeq*. 最终的数组类型是在运行时通过分析数组头的前 4 个字节判断 2.Cxcore 数组操作 初始化 CreateImage 创建头并分配数据 IplImage* cvcreateimage( CvSize size, int depth, int channels ); size 图像宽 高. depth 图像元素的位深度, 可以是下面的其中之一 : IPL_DEPTH_8U - 无符号 8 位整型 IPL_DEPTH_8S - 有符号 8 位整型 IPL_DEPTH_16U - 无符号 16 位整型 IPL_DEPTH_16S - 有符号 16 位整型 IPL_DEPTH_32S - 有符号 32 位整型 IPL_DEPTH_32F - 单精度浮点数 IPL_DEPTH_64F - 双精度浮点数 channels 每个元素 ( 像素 ) 的颜色通道数量. 可以是 1, 2, 3 或 4. 通道是交叉存取的, 例如通常的彩色图像数据排列是 : b0 g0 r0 b1 g1 r1... 虽然通常 IPL 图象格式可以存贮非交叉存取的图像, 并且一些 OpenCV 也能处理他, 但是这个函数只能创建交叉存取图像. 函数 cvcreateimage 创建头并分配数据, 这个函数是下列的缩写型式 header = cvcreateimageheader(size,depth,channels); cvcreatedata(header); // 只是创建空间, 并不会初始化空间内的数据 CreateImageHeader 分配, 初始化, 并且返回 IplImage 结构 65

66 IplImage* cvcreateimageheader( CvSize size, int depth, int channels ); size 图像宽 高. depth 像深 ( 见 CreateImage). channels 通道数 ( 见 CreateImage). 函数 cvcreateimageheader 分配, 初始化, 并且返回 IplImage 结构. 这个函数相似于 : iplcreateimageheader( channels, 0, depth, channels == 1? "GRAY" : "RGB", channels == 1? "GRAY" : channels == 3? "BGR" : channels == 4? "BGRA" : "", IPL_DATA_ORDER_PIXEL, IPL_ORIGIN_TL, 4, size.width, size.height, 0,0,0,0); 然而 IPL 函数不是作为默认的 ( 见 CV_TURN_ON_IPL_COMPATIBILITY 宏 ) ReleaseImageHeader 释放头 void cvreleaseimageheader( IplImage** image ); image 双指针指向头内存分配单元. 函数 cvreleaseimageheader 释放头. 相似于 if( image ) ipldeallocate( *image, IPL_IMAGE_HEADER IPL_IMAGE_ROI ); *image = 0; } 然而 IPL 函数不是作为默认的 ( 见 CV_TURN_ON_IPL_COMPATIBILITY 宏 ) ReleaseImage 释放头和图像数据 66

67 void cvreleaseimage( IplImage** image ); image 双指针指向图像内存分配单元 函数 cvreleaseimage 释放头和图像数据, 相似于 : if( *image ) cvreleasedata( *image ); cvreleaseimageheader( image ); } InitImageHeader 初始化被用图分配的图像头 IplImage* cvinitimageheader( IplImage* image, CvSize size, int depth, int channels, int origin=0, int align=4 ); image 被初始化的图像头. size 图像的宽高. depth 像深 ( 见 CreateImage). channels 通道数 ( 见 CreateImage). origin IPL_ORIGIN_TL 或 IPL_ORIGIN_BL. align 图像行排列, 典型的 4 或 8 字节. 函数 cvinitimageheader 初始化图像头结构, 指向用户指定的图像并且返回这个指针 CloneImage 制作图像的完整拷贝 IplImage* cvcloneimage( const IplImage* image ); image 原图像. 67

68 函数 cvcloneimage 制作图像的完整拷贝包括头 ROI 和数据 SetImageCOI 基于给定的值设置感兴趣通道 void cvsetimagecoi( IplImage* image, int coi ); image coi 图像头. 感兴趣通道. 函数 cvsetimagecoi 基于给定的值设置感兴趣的通道 值 0 意味着所有的通道都被选定, 1 意味着第一个通道被选定等等 如果 ROI 是 NULL 并且 COI!= 0, ROI 被分配. 然而大多数的 OpenCV 函数不支持 COI, 对于这种状况当处理分离图像 / 矩阵通道时, 可以拷贝 ( 通过 cvcopy 或 cvsplit) 通道来分离图像 / 矩阵, 处理后如果需要可再拷贝 ( 通过 cvcopy 或 cvcvtplanetopix) 回来. GetImageCOI 返回感兴趣通道号 int cvgetimagecoi( const IplImage* image ); image 图像头. 函数 cvgetimagecoi 返回图像的感兴趣通道 ( 当所有的通道都被选中返回值是 0). SetImageROI 基于给定的矩形设置 ' 感兴趣 ' 区域 void cvsetimageroi( IplImage* image, CvRect rect ); image rect 图像. ROI 矩形. 函数 cvsetimageroi 基于给定的矩形设置图像的 ROI( 感兴趣区域 ). 如果 ROI 是 NULL 并且参数 RECT 的值不等于整个图像, ROI 被分配. 不像 COI, 大多数的 OpenCV 函数支持 ROI 并且处理它就像它是一个分离的图像 ( 例如, 所有的像素坐标从 ROI 的左上角或左下角 ( 基于图像的结构 ) 计算 68

69 ResetImageROI 释放图像的 ROI void cvresetimageroi( IplImage* image ); image 图像头. 函数 cvresetimageroi 释放图像 ROI. 释放之后整个图像被认为是全部被选中的 相似的结果可以通过下述办法 cvsetimageroi( image, cvrect( 0, 0, image->width, image->height )); cvsetimagecoi( image, 0 ); 但是后者的变量不分配 image->roi. GetImageROI 返回图像的 ROI 坐标 CvRect cvgetimageroi( const IplImage* image ); image 图像头. 函数 cvgetimageroi 返回图像 ROI 坐标. 如果没有 ROI 则返回矩形值为 cvrect(0,0,image->width,image->height) CreateMat 创建矩阵 CvMat* cvcreatemat( int rows, int cols, int type ); rows cols type 矩阵行数 矩阵列数 矩阵元素类型 通常以 CV_< 比特数 >(S U F)C< 通道数 > 型式描述, 例如 : CV_8UC1 意思是一个 8-bit 无符号单通道矩阵, CV_32SC2 意思是一个 32-bit 有符号二 个通道的矩阵 69

70 函数 cvcreatemat 为新的矩阵分配头和下面的数据, 并且返回一个指向新创建的矩阵的指针 是下列操作的缩写型式 : CvMat* mat = cvcreatematheader( rows, cols, type ); cvcreatedata( mat ); 矩阵按行存贮 所有的行以 4 个字节对齐 CreateMatHeader 创建新的矩阵头 CvMat* cvcreatematheader( int rows, int cols, int type ); rows 矩阵行数. cols type 矩阵列数. 矩阵元素类型 ( 见 cvcreatemat). 函数 cvcreatematheader 分配新的矩阵头并且返回指向它的指针. 矩阵数据可被进一步的分配, 使用 cvcreatedata 或通过 cvsetdata 明确的分配数据. ReleaseMat 删除矩阵 void cvreleasemat( CvMat** mat ); mat 双指针指向矩阵. 函数 cvreleasemat 缩减矩阵数据参考计数并且释放矩阵头 : if( *mat ) cvdecrefdata( *mat ); cvfree( (void**)mat ); InitMatHeader 70

71 初始化矩阵头 CvMat* cvinitmatheader( CvMat* mat, int rows, int cols, int type, void* data=null, int step=cv_autostep ); mat rows cols type data step 指针指向要被初始化的矩阵头. 矩阵的行数. 矩阵的列数. 矩阵元素类型. 可选的, 将指向数据指针分配给矩阵头. 排列后的数据的整个行宽, 默认状态下, 使用 STEP 的最小可能值 也就是说默认情况下假定矩阵的行与行之间无隙. 函数 cvinitmatheader 初始化已经分配了的 CvMat 结构. 它可以被 OpenCV 矩阵函数用于处理原始数据 例如, 下面的代码计算通用数组格式存贮的数据的矩阵乘积. 计算两个矩阵的积 double a[] = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; double b[] = 1, 5, 9, 2, 6, 10, 3, 7, 11, 4, 8, 12 }; double c[9]; CvMat Ma, Mb, Mc ; cvinitmatheader( &Ma, 3, 4, CV_64FC1, a ); cvinitmatheader( &Mb, 4, 3, CV_64FC1, b ); cvinitmatheader( &Mc, 3, 3, CV_64FC1, c ); cvmatmuladd( &Ma, &Mb, 0, &Mc ); 71

72 // c 数组存贮 a(3x4) 和 b(4x3) 矩阵的积 Mat 初始化矩阵的头 ( 轻磅变量 ) CvMat cvmat( int rows, int cols, int type, void* data=null ); rows cols type data 矩阵行数列数. 元素类型 ( 见 CreateMat). 可选的分配给矩阵头的数据指针. 函数 cvmat 是个一快速内连函数, 替代函数 cvinitmatheader. 也就是说它相当于 : CvMat mat; cvinitmatheader( &mat, rows, cols, type, data, CV_AUTOSTEP ); CloneMat 创建矩阵拷贝 CvMat* cvclonemat( const CvMat* mat ); mat 输入矩阵. 函数 cvclonemat 创建输入矩阵的一个拷贝并且返回该矩阵的指针. CreateMatND 创建多维密集数组 CvMatND* cvcreatematnd( int dims, const int* sizes, int type ); dims sizes 数组维数. 但不许超过 CV_MAX_DIM ( 默认 =32, 但这个默认值可能在编译时被改变 ) 的定义 72

73 type 数组的维大小. 数组元素类型. 与 CvMat 相同 函数 cvcreatematnd 分配头给多维密集数组并且分配下面的数据, 返回指向被创建数组的指针. 是下列的缩减形式 : CvMatND* mat = cvcreatematndheader( dims, sizes, type ); cvcreatedata( mat ); 矩阵按行存贮. 所有的行以 4 个字节排列 CreateMatNDHeader 创建新的数组头 CvMatND* cvcreatematndheader( int dims, const int* sizes, int type ); dims sizes type 数组维数. 维大小. 数组元素类型. 与 CvMat 相同 函数 cvcreatematnd 分配头给多维密集数组 数组数据可以用 cvcreatedata 进一步的被分配或利用 cvsetdata 由用户明确指定. ReleaseMatND 删除多维数组 void cvreleasematnd( CvMatND** mat ); mat 指向数组的双指针. 函数 cvreleasematnd 缩减数组参考计数并释放数组头 : if( *mat ) cvdecrefdata( *mat ); cvfree( (void**)mat ); 73

74 InitMatNDHeader 初始化多维数组头 CvMatND* cvinitmatndheader( CvMatND* mat, int dims, const int* sizes, int type, void* data=null ); mat dims sizes type data 指向要被出初始化的数组头指针. 数组维数. 维大小. 数组元素类型. 与 CvMat 相同可选的分配给矩阵头的数据指针. 函数 cvinitmatndheader 初始化用户指派的 CvMatND 结构. CloneMatND 创建多维数组的完整拷贝 CvMatND* cvclonematnd( const CvMatND* mat ); mat 输入数组 函数 cvclonematnd 创建输入数组的拷贝并返回指针. DecRefData 缩减数组数据的引用计数 void cvdecrefdata( CvArr* arr ); arr 数组头. 如果引用计数指针非 NULL, 函数 cvdecrefdata 缩减 CvMat 或 CvMatND 数据的引用计数, 如果计数到 0 就删除数据 在当前的版本中只有当数据是用 cvcreatedata 分配的引用计数才会是非 NULL 在其他的情况下比如: 使用 cvsetdata 指派外部数据给矩阵头 ; 代表部分大的矩阵或图像的矩阵头 ; 74

75 是从图像头或 N 维矩阵头转换过来的矩阵头, 在这些情况下引用计数被设置成 NULL 因此不会被缩减 无论数据是否被删除, 数据指针和引用计数指针都将被这个函数清空 IncRefData 增加数组数据的引用计数 int cvincrefdata( CvArr* arr ); arr 数组头. 函数 cvincrefdata 增加 CvMat 或 CvMatND 数据引用计数, 如果引用计数非空返回新的计数值否则返回 0 CreateData 分配数组数据 void cvcreatedata( CvArr* arr ); arr 数组头. 函数 cvcreatedata 分配图像, 矩阵或多维数组数据. 对于矩阵类型使用 OpenCV 的分配函数, 对于 IplImage 类型如果 CV_TURN_ON_IPL_COMPATIBILITY 没有被调用也是可以使用这种方法的反之使用 IPL 函数分配数据 ReleaseData 释放数组数据 void cvreleasedata( CvArr* arr ); arr 数组头 函数 cvreleasedata 释放数组数据. 对于 CvMat 或 CvMatND 结构只需调用 cvdecrefdata(), 也就是说这个函数不能删除外部数据 见 cvcreatedata. SetData 指派用户数据给数组头 75

76 void cvsetdata( CvArr* arr, void* data, int step ); arr data step 数组头. 用户数据. 整行字节长. 函数 cvsetdata 指派用户数据给数组头. 头应该已经使用 cvcreate*header, cvinit*header 或 cvmat ( 对于矩阵 ) 初始化过. GetRawData 返回数组的底层信息 void cvgetrawdata( const CvArr* arr, uchar** data, int* step=null, CvSize* roi_size=null ); arr 数组头. data 输出指针, 指针指向整个图像的结构或 ROI step 输出行字节长 roi_size 输出 ROI 尺寸函数 cvgetrawdata 添充给输出变量数组的底层信息 所有的输出参数是可选的, 因此这些指针可设为 NULL 如果数组是设置了 ROI 的 IplImage 结构, ROI 参数被返回 注意 : 输出指针指向数组头的对应的内存, 不能释放 建议用 memcpy 接下来的例子展示怎样利用这个函数去访问数组元素 使用 GetRawData 计算单通道浮点数组的元素绝对值 float* data; int step; CvSize size; int x, y; cvgetrawdata( array, (uchar**)&data, &step, &size ); 76

77 step /= sizeof(data[0]); for( y = 0; y < size.height; y++, data += step ) for( x = 0; x < size.width; x++ ) data[x] = (float)fabs(data[x]); GetMat 从不确定数组返回矩阵头 CvMat* cvgetmat( const CvArr* arr, CvMat* header, int* coi=null, int allownd=0 ); arr 输入数组. header 指向 CvMat 结构的指针, 作为临时缓存. coi 可选的输出参数, 用于输出 COI. allownd 如果非 0, 函数就接收多维密集数组 (CvMatND*) 并且返回 2D ( 如果 CvMatND 是二维的 ) 或 1D 矩阵 ( 当 CvMatND 是一维或多于二维 ). 数组必须是连续的. 函数 cvgetmat 从输入的数组生成矩阵头, 输入的数组可以是 - CvMat 结构, IplImage 结构或多维密集数组 CvMatND* ( 后者只有当 allownd!= 0 时才可以使用 ). 如果是矩阵函数只是返回指向矩阵的指针. 如果是 IplImage* 或 CvMatND* 函数用当前图像的 ROI 初始化头结构并且返回指向这个临时结构的指针 因为 CvMat 不支持 COI, 所以他们的返回结果是不同的. 这个函数提供了一个简单的方法, 用同一代码处理 IplImage 和 CvMat 二种数据类型 这个函数的反向转换可以用 cvgetimage 将 CvMat 转换成 IplImage. 输入的数组必须有已分配好的底层数据或附加的数据, 否则该函数将调用失败如果输入的数组是 IplImage 格式, 使用平面式数据编排并设置了 COI, 函数返回的指针指向被选定的平面并设置 COI=0. 利用 OPENCV 函数对于多通道平面编排图像可以处理每个平面 GetImage 从不确定数组返回图像头 IplImage* cvgetimage( const CvArr* arr, IplImage* image_header ); arr 输入数组. image_header 77

78 指向 IplImage 结构的指针, 该结构存贮在一个临时缓存. 函数 cvgetimage 从输出数组获得图头, 该数组可以是矩阵 - CvMat*, 或图像 - IplImage* 如果是图像的话函数只是返回输入参数的指针, 如果是 CvMat* 的话函数用输入参数矩阵初始化图像头 因此如果我们把 IplImage 转换成 CvMat 然后再转换 CvMat 回 IplImage, 如果 ROI 被设置过了我们可能会获得不同的头, 这样一些计算图像跨度的 IPL 函数就会失败 [ ] CreateSparseMat 创建稀疏数组 CvSparseMat* cvcreatesparsemat( int dims, const int* sizes, int type ); dims sizes type 数组维数 相对于密集型矩阵, 稀疏数组的维数是不受限制的 ( 最多可达 2 16 ) 数组的维大小 数组元素类型, 见 CvMat 函数 cvcreatesparsemat 分配多维稀疏数组 刚初始化的数组不含元素, 因此 cvget*d 或 cvgetreal*d 函数对所有索引都返回 0 [ ] ReleaseSparseMat 删除稀疏数组 void cvreleasesparsemat( CvSparseMat** mat ); mat 双指针指向数组 函数 cvreleasesparsemat 释放稀疏数组并清空数组指针 [ ] CloneSparseMat 创建稀疏数组的拷贝 CvSparseMat* cvclonesparsemat( const CvSparseMat* mat ); mat 输入数组 函数 cvclonesparsemat 创建输入数组的拷贝并返回指向这个拷贝的指针 [ ] 获取元素和数组子集 78

79 [ ] GetSubRect 返回输入的图像或矩阵的矩形数组子集的矩阵头 CvMat* cvgetsubrect( const CvArr* arr, CvMat* submat, CvRect rect ); arr submat rect 输入数组 指向矩形数组子集矩阵头的指针 以 0 坐标为基准的 ROI 函数 cvgetsubrect 根据指定的数组矩形返回矩阵头, 换句话说, 函数允许像处理一个独立数组一样处理输入数组的一个指定子矩形 函数在处理时要考虑进输入数组的 ROI, 因此数组的 ROI 是实际上被提取的 [ ] GetRow, GetRows 返回数组的一行或在一定跨度内的行 CvMat* cvgetrow( const CvArr* arr, CvMat* submat, int row ); CvMat* cvgetrows( const CvArr* arr, CvMat* submat, int start_row, int end_row, int delta_row=1 ); arr 输入数组 submat 指向返回的子数组头的指针 row 被选定行的索引下标, 索引下标从 0 开始 start_row 跨度的开始行 ( 包括此行 ) 索引下标, 索引下标从 0 开始 end_row 跨度的结束行 ( 不包括此行 ) 索引下标, 索引下标从 0 开始 delta_row 在跨度内的索引下标跨步, 从开始行到结束行每隔 delta_row 行提取一行 函数 GetRow 和 GetRows 返回输入数组中指定的一行或在一定跨度内的行对应的数组头 注意 GetRow 实际上是以下 cvgetrows 调用的简写 : cvgetrow( arr, submat, row ) ~ cvgetrows( arr, submat, row, row + 1, 1 ); [ ] 79

80 GetCol, GetCols 返回数组的一列或一定跨度内的列 CvMat* cvgetcol( const CvArr* arr, CvMat* submat, int col ); CvMat* cvgetcols( const CvArr* arr, CvMat* submat, int start_col, int end_col ); arr 输入数组 submat 指向结果子数组头的指针 col 被选定列的索引下标, 索引下标从 0 开始 start_col 跨度的开始列 ( 包括该列 ) 索引下标, 索引下标从 0 开始 end_col 跨度的结束列 ( 不包括该列 ) 索引下标, 索引下标从 0 开始 函数 GetCol 和 GetCols 根据指定的列 / 列跨度返回对应的数组头 注意 GetCol 实际上是以下 cvgetcols 调用的简写形式 : cvgetcol( arr, submat, col ); // ~ cvgetcols( arr, submat, col, col + 1 ); [ ] GetDiag 返回一个数组对角线 CvMat* cvgetdiag( const CvArr* arr, CvMat* submat, int diag=0 ); arr submat diag 输入数组. 指向结果子集的头指针. 数组对角线 0 是主对角线,-1 是主对角线上面对角线,1 是主对角线下对角线, 以此类推 函数 cvgetdiag 根据指定的 diag 参数返回数组的对角线头 [ ] GetSize 返回矩阵或图像 ROI 的大小 CvSize cvgetsize( const CvArr* arr ); 80

81 arr 数组头 函数 cvgetsize 返回图像或矩阵的行数和列数, 如果是图像就返回 ROI 的大小 [ ] InitSparseMatIterator 初始化稀疏数组元素迭代器 CvSparseNode* cvinitsparsematiterator( const CvSparseMat* mat, CvSparseMatIterator* mat_iterator ); mat 输入的数组. mat_iterator 被初始化的迭代器. 函数 cvinitsparsematiterator 初始化稀疏数组元素的迭代器并且返回指向第一个元素的指针, 如果数组为空则返回 NULL [ ] GetNextSparseNode 初始化稀疏数组元素迭代器 CvSparseNode* cvgetnextsparsenode( CvSparseMatIterator* mat_iterator ); mat_iterator 稀疏数组的迭代器函数 cvgetnextsparsenode 移动迭代器到下一个稀疏矩阵元素并返回指向他的指针 在当前的版本不存在任何元素的特殊顺序, 因为元素是按 HASH 表存贮的下面的列子描述怎样在稀疏矩阵上迭代 : 利用 cvinitsparsematiterator 和 cvgetnextsparsenode 计算浮点稀疏数组的和 double sum; int i, dims = cvgetdims( array ); CvSparseMatIterator mat_iterator; CvSparseNode* node = cvinitsparsematiterator( array, &mat_iterator ); for( ; node!= 0; node = cvgetnextsparsenode( &mat_iterator )) int* idx = CV_NODE_IDX( array, node ); /* get pointer to the element indices */ 81

82 float val = *(float*)cv_node_val( array, node ); /* get value of the element (assume that the type is CV_32FC1) */ printf( "(" ); for( i = 0; i < dims; i++ ) printf( "%4d%s", idx[i], i < dims - 1? "," : "): " ); printf( "%g\n", val ); sum += val; printf( "\ntotal sum = %g\n", sum ); [ ] GetElemType 返回数组元素类型 int cvgetelemtype( const CvArr* arr ); arr 输入数组. 函数 GetElemType 返回数组元素类型就像在 cvcreatemat 中讨论的一样 : CV_8UC1... CV_64FC4 [ ] GetDims, GetDimSize 返回数组维数和他们的大小或者特殊维的大小 int cvgetdims( const CvArr* arr, int* sizes=null ); int cvgetdimsize( const CvArr* arr, int index ); arr sizes index 输入数组. 可选的输出数组维尺寸向量, 对于 2D 数组第一位是数组行数 ( 高 ), 第二位是数组列数 ( 宽 ) 以 0 为基准的维索引下标 ( 对于矩阵 0 意味着行数,1 意味着列数, 对于图象 0 意味着高,1 意味着宽 函数 cvgetdims 返回维数和他们的大小 如果是 IplImage 或 CvMat 总是返回 2, 不管图像 / 矩阵行数 函数 cvgetdimsize 返回特定的维大小 ( 每维的元素数 ) 例如, 接下来的代码使用二种方法计算数组元素总数 82

83 // via cvgetdims() int sizes[cv_max_dim]; int i, total = 1; int dims = cvgetdims( arr, size ); for( i = 0; i < dims; i++ ) total *= sizes[i]; // via cvgetdims() and cvgetdimsize() int i, total = 1; int dims = cvgetdims( arr ); for( i = 0; i < dims; i++ ) total *= cvgetdimssize( arr, i ); [ ] Ptr*D 返回指向特殊数组元素的指针 uchar* cvptr1d( const CvArr* arr, int idx0, int* type=null ); uchar* cvptr2d( const CvArr* arr, int idx0, int idx1, int* type=null ); uchar* cvptr3d( const CvArr* arr, int idx0, int idx1, int idx2, int* type=null ); uchar* cvptrnd( const CvArr* arr, int* idx, int* type=null, int create_node=1, unsigned* precalc_hashval=null ); arr 输入数组. idx0 元素下标的第一个以 0 为基准的成员 idx1 元素下标的第二个以 0 为基准的成员 idx2 元素下标的第三个以 0 为基准的成员 idx 数组元素下标 type 可选的, 矩阵元素类型输出参数 create_node 可选的, 为稀疏矩阵输入的参数 如果这个参数非零就意味着被需要的元素如果不存在就会被创建 precalc_hashval 可选的, 为稀疏矩阵设置的输入参数 如果这个指针非 NULL, 函数不会重新计算节点的 HASH 值, 而是从指定位置获取 这种方法有利于提高智能组合数据的操作 (TODO: 提供了一个例子 ) 83

84 函数 cvptr*d 返回指向特殊数组元素的指针 数组维数应该与转递给函数物下标数相匹配, 除了 cvptr1d 函数, 它可以被用于顺序存取的 1D,2D 或 nd 密集数组函数也可以用于稀疏数组, 并且如果被需要的节点不存在函数可以创建这个节点并设置为 0 就像其它获取数组元素的函数 (cvget[real]*d, cvset[real]*d) 如果元素的下标超出了范围就会产生错误 [ ] Get*D 返回特殊的数组元素 CvScalar cvget1d( const CvArr* arr, int idx0 ); CvScalar cvget2d( const CvArr* arr, int idx0, int idx1 ); CvScalar cvget3d( const CvArr* arr, int idx0, int idx1, int idx2 ); CvScalar cvgetnd( const CvArr* arr, int* idx ); arr idx0 idx1 idx2 idx 输入数组. 元素下标第一个以 0 为基准的成员元素下标第二个以 0 为基准的成员元素下标第三个以 0 为基准的成员元素下标数组 函数 cvget*d 返回指定的数组元素 对于稀疏数组如果需要的节点不存在函数返回 0 ( 不会创建新的节点 ) [ ] GetReal*D 返回单通道数组的指定元素 double cvgetreal1d( const CvArr* arr, int idx0 ); double cvgetreal2d( const CvArr* arr, int idx0, int idx1 ); double cvgetreal3d( const CvArr* arr, int idx0, int idx1, int idx2 ); double cvgetrealnd( const CvArr* arr, int* idx ); arr idx0 idx1 输入数组, 必须是单通道. 元素下标的第一个成员, 以 0 为基准 84

85 idx2 idx 元素下标的第二个成员, 以 0 为基准 元素下标的第三个成员, 以 0 为基准 元素下标数组 函数 cvgetreal*d 返回单通道数组的指定元素, 如果数组是多通道的, 就会产生运行时错误, 而 cvget*d 函数可以安全的被用于单通道和多通道数组, 但他们运行时会有点慢如果指定的点不存在对于稀疏数组点会返回 0( 不会创建新的节点 ) [ ] mget 返回单通道浮点矩阵指定元素 double cvmget( const CvMat* mat, int row, int col ); mat row col 输入矩阵. 行下标, 以 0 为基点. 列下标, 以 0 为基点 函数 cvmget 是 cvgetreal2d 对于单通道浮点矩阵的快速替代函数, 函数运行比较快速因为它是内连函数, 这个函数对于数组类型 数组元素类型的检查作的很少, 并且仅在调式模式下检查数的行和列范围 [ ] Set*D 修改指定的数组 void cvset1d( CvArr* arr, int idx0, CvScalar value ); void cvset2d( CvArr* arr, int idx0, int idx1, CvScalar value ); void cvset3d( CvArr* arr, int idx0, int idx1, int idx2, CvScalar value ); void cvsetnd( CvArr* arr, int* idx, CvScalar value ); arr idx0 idx1 idx2 idx 输入数组元素下标的第一个成员, 以 0 为基点元素下标的第二个成员, 以 0 为基点元素下标的第三个成员, 以 0 为基点 85

86 value 元素下标数组 指派的值 函数 cvset*d 指定新的值给指定的数组元素 对于稀疏矩阵如果指定节点不存在函数创建新的节点 [ ] SetReal*D 修改指定数组元素值 void cvsetreal1d( CvArr* arr, int idx0, double value ); void cvsetreal2d( CvArr* arr, int idx0, int idx1, double value ); void cvsetreal3d( CvArr* arr, int idx0, int idx1, int idx2, double value ); void cvsetrealnd( CvArr* arr, int* idx, double value ); arr idx0 idx1 idx2 idx value 输入数组. 元素下标的第一个成员, 以 0 为基点元素下标的第二个成员, 以 0 为基点元素下标的第三个成员, 以 0 为基点元素下标数组指派的值 函数 cvsetreal*d 分配新的值给单通道数组的指定元素, 如果数组是多通道就会产生运行时错误 然而 cvset*d 可以安全的被用于多通道和单通道数组, 只是稍微有点慢 对于稀疏数组如果指定的节点不存在函数会创建该节点 [ ] mset 为单通道浮点矩阵的指定元素赋值 void cvmset( CvMat* mat, int row, int col, double value ); mat row col 矩阵. 行下标, 以 0 为基点. 列下标, 以 0 为基点. 86

87 value 矩阵元素的新值 函数 cvmset 是 cvsetreal2d 快速替代, 对于单通道浮点矩阵因为这个函数是内连的所以比较快, 函数对于数组类型 数组元素类型的检查作的很少, 并且仅在调式模式下检查数的行和列范围 [ ] ClearND 清除指定数组元素 void cvclearnd( CvArr* arr, int* idx ); arr idx 输入数组. 数组元素下标 函数 cvclearnd 清除指定密集型数组的元素 ( 置 0) 或删除稀疏数组的元素, 如果元素不存在函数不作任何事 [ ] 拷贝和添加 [ ] Copy 拷贝一个数组给另一个数组 void cvcopy( const CvArr* src, CvArr* dst, const CvArr* mask=null ); src dst mask [ ] Set 输入数组 输出数组 操作掩码是 8 比特单通道的数组, 它指定了输出数组中被改变的元素 函数 cvcopy 从输入数组中复制选定的成分到输出数组 : 如果 mask(i)!=0, 则 dst(i)=src(i) 如果输入输出数组中的一个是 IplImage 类型的话, 其 ROI 和 COI 将被使用 输入输出数组必须是同样的类型 维数和大小 函数也可以用来复制散列数组 ( 这种情况下不支持 mask) 设置数组所有元素为指定值 87

88 void cvset( CvArr* arr, CvScalar value, const CvArr* mask=null ); arr value mask 输出数组 填充值 操作掩码是 8 比特单通道的数组, 它指定了输出数组中被改变的元素 函数 cvset 拷贝数量值到输出数组的每一个被除数选定的元素 : arr(i)=value if mask(i)!=0 如果数组 arr 是 IplImage 类型, 那么就会使用 ROI, 但 COI 不能设置 [ ] SetZero 清空数组 void cvsetzero( CvArr* arr ); #define cvzero cvsetzero arr 要被清空数组. 函数 cvsetzero 清空数组. 对于密集型号数组 (CvMat, CvMatND or IplImage) cvzero(array) 就相当于 cvset(array,cvscalarall(0),0), 对于稀疏数组所有的元素都将被删除. [ ] SetIdentity 初始化带尺度的单位矩阵 void cvsetidentity( CvArr* mat, CvScalar value=cvrealscalar(1) ); mat value 待初始化的矩阵 ( 不一定是方阵 ) 赋值给对角线元素的值 函数 cvsetidentity 初始化带尺度的单位矩阵 : [ ] Range arr(i,j)=value 如果 i=j, 否则为 0 88

89 用指定范围的数来填充矩阵. void cvrange( CvArr* mat, double start, double end ); mat start end 即将被初始化的矩阵, 必须是指向单通道的 32 位 ( 整型或浮点型 ) 的矩阵的指针. 指定范围的最小边界 指定范围的最大边界 该函数按以下方式初始化矩阵 : arr(i,j)=(end-start)*(i*cols(arr)+j)/(cols(arr)*rows(arr)) 例如 : 以下的代码将按相应的整型数初始化一维向量 : CvMat* A = cvcreatemat( 1, 10, CV_32S ); cvrange( A, 0, A->cols ); //A 将被初始化为 [0,1,2,3,4,5,6,7,8,9] [ ] 变换和置换 [ ] Reshape 不拷贝数据修改矩阵 / 图像形状 CvMat* cvreshape( const CvArr* arr, CvMat* header, int new_cn, int new_rows=0 ); arr 输入的数组. header 被添充的矩阵头 new_cn 新的通道数.new_cn = 0 意味着不修改通道数 new_rows 新的行数. 如果 new_rows = 0 保持原行数不修改否则根据 new_cn 值修改输出数组函数 cvreshape 初始化 CvMat 头 header 以便于让头指向修改后的形状 ( 但数据保持原样 )- 也就是说修改通道数, 修改行数或者两者者改变. 例如, 接下来的代码创建一个图像缓存 两个图像头, 第一个是 320x240x3 图像第二个是 960x240x1 图像 : IplImage* color_img = cvcreateimage( cvsize(320,240), IPL_DEPTH_8U, 3 ); CvMat gray_mat_hdr; IplImage gray_img_hdr, *gray_img; 89

90 cvreshape( color_img, &gray_mat_hdr, 1 ); gray_img = cvgetimage( &gray_mat_hdr, &gray_img_hdr ); 下一个例子转换 3x3 矩阵成单向量 1x9 CvMat* mat = cvcreatemat( 3, 3, CV_32F ); CvMat row_header, *row; row = cvreshape( mat, &row_header, 0, 1 ); [ ] ReshapeMatND 修改多维数组形状, 拷贝 / 不拷贝数据 CvArr* cvreshapematnd( const CvArr* arr, int sizeof_header, CvArr* header, int new_cn, int new_dims, int* new_sizes ); #define cvreshapend( arr, header, new_cn, new_dims, new_sizes ) \ cvreshapematnd( (arr), sizeof(*(header)), (header), \ (new_cn), (new_dims), (new_sizes)) arr 输入数组 sizeof_header 输出头的大小, 对于 IplImage, CvMat 和 CvMatND 各种结构输出的头均是不同的. header 被添充的输出头. new_cn 新的通道数, 如果 new_cn = 0 则通道数保持原样 new_dims 新的维数. 如果 new_dims = 0 则维数保持原样 new_sizes 新的维大小. 只有当 new_dims=1 值被使用, 因为要保持数组的总数一致, 因此如果 new_dims = 1, new_sizes 是不被使用的函数 cvreshapematnd 是 cvreshape 的高级版本, 它可以处理多维数组 ( 能够处理通用的图像和矩阵 ) 并且修改维数, 下面的是使用 cvreshapematnd 重写 cvreshape 的二个例子 : IplImage* color_img = cvcreateimage( cvsize(320,240), IPL_DEPTH_8U, 3 ); IplImage gray_img_hdr, *gray_img; gray_img = (IplImage*)cvReshapeND( color_img, &gray_img_hdr, 1, 0, 0 ); 90

91 ... /*second example is modified to convert 2x2x2 array to 8x1 vector */ int size[] = 2, 2, 2 }; CvMatND* mat = cvcreatematnd( 3, size, CV_32F ); CvMat row_header, *row; row = cvreshapend( mat, &row_header, 0, 1, 0 ); [ ] Repeat 用原数组管道式添充输出数组 void cvrepeat( const CvArr* src, CvArr* dst ); src dst 输入数组, 图像或矩阵 输出数组, 图像或矩阵 函数 cvrepeat 使用被管道化的原数组添充输出数组 : dst(i,j)=src(i mod rows(src), j mod cols(src)) 因此, 输出数组可能小于也可能大于输入数组. [ ] Flip 垂直, 水平或即垂直又水平翻转二维数组 void cvflip( const CvArr* src, CvArr* dst=null, int flip_mode=0); #define cvmirror cvflip src 原数组. dst 目标责任制数组. 如果 dst = NULL 翻转是在内部替换. flip_mode 指定怎样去翻转数组 flip_mode = 0 沿 X- 轴翻转, flip_mode > 0 ( 如 1) 沿 Y- 轴翻转, flip_mode < 0 ( 如 -1) 沿 X- 轴和 Y- 轴翻转. 见下面的公式 91

92 函数 cvflip 以三种方式之一翻转数组 ( 行和列下标是以 0 为基点的 ): dst(i,j)=src(rows(src)-i-1,j) if flip_mode = 0 dst(i,j)=src(i,cols(src1)-j-1) if flip_mode > 0 dst(i,j)=src(rows(src)-i-1,cols(src)-j-1) if flip_mode < 0 函数主要使用在 : 垂直翻转图像 (flip_mode = 0) 用于顶 - 左和底 - 左图像结构的转换, 主要用于 WIN32 系统下的视频操作处理. 水平图像转换, 使用连续的水平转换和绝对值差检查垂直轴对称 (flip_mode > 0) 水平和垂直同时转换, 用于连续的水平转换和绝对真理值差检查中心对称 s(flip_mode < 0) 翻转 1 维指针数组的顺序 (flip_mode > 0) [ ] Split 分割多通道数组成几个单通道数组或者从数组中提取一个通道 void cvsplit( const CvArr* src, CvArr* dst0, CvArr* dst1, CvArr* dst2, CvArr* dst3 ); #define cvcvtpixtoplane cvsplit src 原数组. dst0...dst3 目标通道函数 cvsplit 分割多通道数组成分离的单通道数组 d 可获得两种操作模式. 如果原数组有 N 通道且前 N 输出数组非 NULL, 所有的通道都会被从原数组中提取, 如果前 N 个通道只有一个通道非 NULL 函数只提取该指定通道, 否则会产生一个错误, 馀下的通道 ( 超过前 N 个通道的以上的 ) 必须被设置成 NULL, 对于设置了 COI 的 IplImage 结使用 cvcopy 也可以从图像中提取单通道 [ ] Merge 从几个单通道数组组合成多通道数组或插入一个单通道数组 void cvmerge( const CvArr* src0, const CvArr* src1, const CvArr* src2, const CvArr* src3, CvArr* dst ); 92

93 #define cvcvtplanetopix cvmerge src0... src3 输入的通道. dst 输出数组. 函数 cvmerge 是前一个函数的反向操作 如果输出数组有 N 个通道并且前 N 个输入通道非 NULL, 就拷贝所有通道到输出数组, 如果在前 N 个通道中只有一个单通道非 NULL, 只拷贝这个通道到输出数组, 否则就会产生错误 除前 N 通道以外的馀下的通道必须置 NULL 对于设置了 COI 的 IplImage 结构使用 cvcopy 也可以实现向图像中插入一个通道 [ ] MixChannels 拷贝输入数组的若干个通道到输出数组的某些通道上面. void cvmixchannels( const CvArr** src, int src_count, CvArr** dst, int dst_count, const int* from_to, int pair_count ); src 输入数组 src_count 输入数组的个数 dst 输出数组 dst_count 输出数组的个数 from_to 对数的阵列 pair_count from_to 里面的对数的个数, 或者说被拷贝的位面的个数. [ ] RandShuffle 随机交换数组的元素 void cvrandshuffle( CvArr* mat, CvRNG* rng, double iter_factor=1.); mat rng 输入的矩阵, 用来被随机处理. 随机数产生器用来随机交换数组元素. 如果为 NULL, 一个当前的随机数发生器将被创建与使用. 93

94 iter_factor 相关的参数, 用来刻划交换操作的密度. 请看下面的说明. 这个函数在每个反复的操作中交换随机选择的矩阵里面的元素 ( 在多通道的数组里面每个元素可能包括若干个部分 ), 反复的次数 ( 也就是交换的对数 ) 等于 round(iter_factor*rows(mat)*cols(mat)), 因此如果 iter_factor=0, 没有交换产生, 如果等于 1 意味着随机交换了 rows(mat)*cols(mat) 对数. [ ] 算术, 逻辑和比较 [ ] LUT 利用查找表转换数组 void cvlut( const CvArr* src, CvArr* dst, const CvArr* lut ); src dst lut 元素为 8 位的原数组 与原数组有相同通道数的输出数组, 深度不确定 有 256 个元素的查找表 ; 必须要与原输出数组有相同像深 函数 cvlut 使用查找表中的值添充输出数组. 坐标入口来自于原数组, 也就是说函数处理每个元素按如下方式 : dst(i)=lut[src(i)+delta] 这里当 src 的深度是 CV_8U 时 DELTA=0,src 的深度是 CV_8S 时 DELTA=128 [ ] ConvertScale 使用线性变换转换数组 void cvconvertscale( const CvArr* src, CvArr* dst, double scale=1, double shift=0 ); #define cvcvtscale cvconvertscale #define cvscale cvconvertscale #define cvconvert( src, dst ) cvconvertscale( (src), (dst), 1, 0 ) src dst scale shift 输入数组. 输出数组 比例因子. 94

95 该加数被加到输入数组元素按比例缩放后得到的元素上函数 cvconvertscale 有多个不同的目的因此就有多个同义函数 ( 如上面的 #define 所示 ) 该函数首先对输入数组的元素进行比例缩放, 然后将 shift 加到比例缩放后得到的各元素上, 即 : dst(i)=src(i)*scale + (shift,shift,...), 最后可选的类型转换将结果拷贝到输出数组 多通道的数组对各个通道是独立处理的 类型转换主要用舍入和溢出截断来完成 也就是如果缩放 + 转换后的结果值不能用输出数组元素类型值精确表达, 就设置成在输出数组数据轴上最接近该数的值 如果 scale=1, shift=0 就不会进行比例缩放. 这是一个特殊的优化, 相当于该函数的同义函数名 :cvconvert 如果原来数组和输出数组的类型相同, 这是另一种特殊情形, 可以被用于比例缩放和平移矩阵或图像, 此时相当于该函数的同义函数名 :cvscale [ ] ConvertScaleAbs 使用线性变换转换输入数组元素成 8 位无符号整型 void cvconvertscaleabs( const CvArr* src, CvArr* dst, double scale=1, double shift=0 ); #define cvcvtscaleabs cvconvertscaleabs src dst scale shift 原数组输出数组 ( 深度为 8u). 比例因子. 原数组元素按比例缩放后添加的值 函数 cvconvertscaleabs 与前一函数是相同的, 但它是存贮变换结果的绝对值 : dst(i)=abs(src(i)*scale + (shift,shift,...)) 函数只支持目标数数组的深度为 8u (8-bit 无符号 ), 对于别的类型函数仿效于 cvconvertscale 和 cvabs 函数的联合 [ ] Add 计算两个数组中每个元素的和 95

96 void cvadd( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=null ); src1 src2 dst mask 第一个原数组第二个原数组输出数组操作的复盖面, 8-bit 单通道数组 ; 只有复盖面指定的输出数组被修改 函数 cvadd 加一个数组到别一个数组中 : dst(i)=src1(i)+src2(i) if mask(i)!=0 除复盖面外所有的数组必须有相同的类型相同的大小 ( 或 ROI 尺寸 ) [ ] AddS 计算数量和数组的和 void cvadds( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=null ); src value dst mask 原数组. 被加入数量输出数组操作的复盖面 (8-bit 单通道数组 ) ; 只有复盖面指定的输出数组被修改 函数 cvadds 用数量值与原数组 src1 的每个元素想加并存贮结果到 dst(i)=src(i)+value if mask(i)!=0 除复盖面外所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] AddWeighted 计算两数组的加权值的和 96

97 void cvaddweighted( const CvArr* src1, double alpha, const CvArr* src2, double beta, double gamma, CvArr* dst ); src1 alpha src2 beta dst gamma 第一个原数组. 第一个数组元素的权值第二个原数组第二个数组元素的权值输出数组添加的常数项 函数 cvaddweighted 计算两数组的加权值的和 : dst(i)=src1(i)*alpha+src2(i)*beta+gamma 所有的数组必须的相同的类型相同的大小 ( 或 ROI 大小 ) [ ] Sub 计算两个数组每个元素的差 void cvsub( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=null ); src1 src2 dst mask 第一个原数组第二个原数组. 输出数组. 操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 cvsub 从一个数组减去别一个数组 : dst(i)=src1(i)-src2(i) if mask(i)!=0 除复盖面外所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) 97

98 [ ] SubS 计算数组和数量之间的差 void cvsubs( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=null ); src value dst mask 原数组. 被减的数量. 输出数组. 操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 cvsubs 从原数组的每个元素中减去一个数量 : dst(i)=src(i)-value if mask(i)!=0 除复盖面外所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] SubRS 计算数量和数组之间的差 void cvsubrs( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=null ); src value dst mask 第一个原数组 被减的数量输出数组操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 cvsubrs 从一个数量减去原数组的每个元素 : dst(i)=value-src(i) if mask(i)!=0 除复盖面外所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] Mul 98

99 计算两个数组中每个元素的积 void cvmul( const CvArr* src1, const CvArr* src2, CvArr* dst, double scale=1 ); src1 src2 dst scale 第一个原数组. 第二个原数组. 输出数组. 设置的比例因子 函数 cvmul 计算两个数组中每个元素的积 : dst(i)=scale src1(i) src2(i) 所有的数组必须有相同的类型和相同的大小 ( 或 ROI 大小 ) [ ] Div 两个数组每个元素相除 void cvdiv( const CvArr* src1, const CvArr* src2, CvArr* dst, double scale=1 ); src1 src2 dst scale 第一个原数组 如该指针为 NULL, 假高该数组的所有元素都为 1 第二个原数组 输出数组设置的比例因子 函数 cvdiv 用一个数组除以另一个数组 : dst(i)=scale src1(i)/src2(i), if src1!=null dst(i)=scale/src2(i),: if src1=null 所有的数组必须有相同的类型和相同的大小 ( 或 ROI 大小 ) [ ] And 计算两个数组的每个元素的按位与 99

100 void cvand( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=null ); src1 src2 dst mask 第一个原数组第二个原数组. 输出数组操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 cvand 计算两个数组的每个元素的按位逻辑与 : dst(i)=src1(i)&src2(i) if mask(i)!=0 对浮点数组按位表示操作是很有利的 除复盖面, 所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] AndS 计算数组每个元素与数量之间的按位与 void cvands( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=null ); src value dst mask 原数组. 操作中用到的数量输出数组操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 AndS 计算数组中每个元素与数量之量的按位与 : dst(i)=src(i)&value if mask(i)!=0 在实际操作之前首先把数量类型转换成与数组相同的类型 对浮点数组按位表示操作是很有利的 除复盖面, 所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) 接下来的例子描述怎样计算浮点数组元素的绝对值, 通过清除最前面的符号位 : 100

101 float a[] = -1, 2, -3, 4, -5, 6, -7, 8, -9 }; CvMat A = cvmat( 3, 3, CV_32F, &a ); int i, abs_mask = 0x7fffffff; cvands( &A, cvrealscalar(*(float*)&abs_mask), &A, 0 ); for( i = 0; i < 9; i++ ) printf("%.1f ", a[i] ); 代码结果是 : [ ] Or 计算两个数组每个元素的按位或 void cvor( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=null ); src1 src2 dst mask 第一个原数组第二个原数组输出数组. 操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 cvor 计算两个数组每个元素的按位或 : dst(i)=src1(i) src2(i) 对浮点数组按位表示操作是很有利的 除复盖面, 所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] OrS 计算数组中每个元素与数量之间的按位或 void cvors( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=null ); src1 原数组 101

102 value dst mask 操作中用到的数量 目数组. 操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 OrS 计算数组中每个元素和数量之间的按位或 : dst(i)=src(i) value if mask(i)!=0 在实际操作之前首先把数量类型转换成与数组相同的类型 对浮点数组按位表示操作是很有利的 除复盖面, 所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] Xor 计算两个数组中的每个元素的按位异或 void cvxor( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=null ); src1 src2 dst mask 第一个原数组第二个原数组. 输出数组操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 cvxor 计算两个数组元素的按位异或 : dst(i)=src1(i) src2(i) if mask(i)!=0 对浮点数组按位表示操作是很有利的 除复盖面, 所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) [ ] XorS 计算数组元素与数量之间的按位异或 void cvxors( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=null ); src value 原数组 102

103 dst mask 操作中用到的数量 输出数组. 操作复盖面 ( 8-bit 单通道数组 ); 只有复盖面指定的输出数组被修改 函数 XorS 计算数组元素与数量之间的按位异或 : dst(i)=src(i) value if mask(i)!=0 在实际操作之前首先把数量类型转换成与数组相同的类型 对浮点数组按位表示操作是很有利的 除复盖面, 所有数组都必须有相同的类型, 相同的大小 ( 或 ROI 大小 ) 下面例子描述怎样对共轭复数向量转换, 通过转换前面的符号位 : float a[] = 1, 0, 0, 1, -1, 0, 0, -1 }; /* 1, j, -1, -j */ CvMat A = cvmat( 4, 1, CV_32FC2, &a ); int i, neg_mask = 0x ; cvxors( &A, cvscalar( 0, *(float*)&neg_mask, 0, 0 ), &A, 0 ); for( i = 0; i < 4; i++ ) printf("(%.1f, %.1f) ", a[i*2], a[i*2+1] ); The code should print: (1.0,0.0) (0.0,-1.0) (-1.0,0.0) (0.0,1.0) [ ] Not 计算数组元素的按位取反 void cvnot( const CvArr* src, CvArr* dst ); src1 dst 原数组 输出数组 函数不取反每个数组元素的每一位 dst(i)=~src(i) [ ] Cmp 103

104 比较两个数组元素 P void cvcmp( const CvArr* src1, const CvArr* src2, CvArr* dst, int cmp_op ); src1 src2 dst cmp_op 第一个原数组 第二个原数组, 这两个数组必须都是单通道数据 输出数组必须是 8u 或 8s 类型. 该标识指定要检查的元素之间的关系 : CV_CMP_EQ - src1(i) " 等于 " src2(i) CV_CMP_GT - src1(i) " 大于 " src2(i) CV_CMP_GE - src1(i) " 大于等于 " src2(i) CV_CMP_LT - src1(i) " 小于 " src2(i) CV_CMP_LE - src1(i) " 小于等于 " src2(i) CV_CMP_NE - src1(i) " 不等于 " src2(i) 函数 cvcmp 比较两个数组的对应元素并且添充输出数组 : dst(i)=src1(i) op src2(i), 这里 op 是 '=', '>', '>=', '<', '<=' or '!='. 如果元素之间的关系为真则设置 dst(i) 为 0xff ( 也就是所有的位都为 '1') 否则为 0 除了输出数组所有数组必须是相同的类型相同的大小 ( 或 ROI 大小 ) CmpS 比较数组的每个元素与数量的关系 void cvcmps( const CvArr* src, double value, CvArr* dst, int cmp_op ); src 原数, 输入数组必须是单通道数据 value 用与数组元素比较的数量值 dst 输出数组必须是 8u 或 8s 类型. cmp_op 该标识指定要检查的元素之间的关系 : CV_CMP_EQ - src1(i) " 等于 " value CV_CMP_GT - src1(i) " 大于 " value CV_CMP_GE - src1(i) " 大于等于 " value CV_CMP_LT - src1(i) " 小于 " value 104

105 CV_CMP_LE - src1(i) " 小于等于 " value CV_CMP_NE - src1(i) " 不等于 " value 函数 cvcmps 比较数组元素与数量并且添充目标复盖面数组 : dst(i)=src(i) op scalar, 这里 op 是 '=', '>', '>=', '<', '<=' or '!='. 如果元素之间的关系为真则设置 dst(i) 为 0xff ( 也就是所有的位都为 '1') 否则为 0 所有的数组必须有相同的大小 ( 或 ROI 大小 ) InRange 检查数组元素是否在两个数组之间 void cvinrange( const CvArr* src, const CvArr* lower, const CvArr* upper, CvArr* dst ); src lower upper dst 第一个原数组包括进的下边界数组不包括进的上边界线数组输出数组必须是 8u 或 8s 类型. 函数 cvinrange 对输入的数组作范围检查, 对于单通道数组 : dst(i)=lower(i)0 <= src(i)0 < upper(i)0 对二通道数组 : dst(i)=lower(i)0 <= src(i)0 < upper(i)0 && lower(i)1 <= src(i)1 < upper(i)1 以此类推如果 src(i) 在范围内 dst(i) 被设置为 0xff ( 每一位都是 '1') 否则置 0 除了输出数组所有数组必须是相同的类型相同的大小 ( 或 ROI 大小 ) InRangeS 检查数组元素是否在两个数量之间 105

106 void cvinranges( const CvArr* src, CvScalar lower, CvScalar upper, CvArr* dst ); src lower upper dst 第一个原数组包括进的下边界. 不包括进的上边界输出数组必须是 8u 或 8s 类型. 函数 cvinranges 检查输入数组元素范围 : 对于单通道数组 : dst(i)=lower0 <= src(i)0 < upper0 对于双通道数组以此类推 : dst(i)=lower0 <= src(i)0 < upper0 && lower1 <= src(i)1 < upper1 如果 src(i) 在范围内 dst(i) 被设置为 0xff ( 每一位都是 '1') 否则置 0 所有的数组必须有相同的大小 ( 或 ROI 大小 ) Max 查找两个数组中每个元素的较大值 void cvmax( const CvArr* src1, const CvArr* src2, CvArr* dst ); src1 src2 dst 第一个原数组 第二个原数组 输出数组 函数 cvmax 计算两个数组中每个元素的较大值 : dst(i)=max(src1(i), src2(i)) 所有的数组必须的一个单通道, 相同的数据类型和相同的大小 ( 或 ROI 大小 ) 106

107 MaxS 查找数组元素与数量之间的较大值 void cvmaxs( const CvArr* src, double value, CvArr* dst ); src value dst 第一个原数组. 数量值. 输出数组 函数 cvmaxs 计算数组元素和数量之间的较大值 : dst(i)=max(src(i), value) 所有数组必须有一个单一通道, 相同的数据类型相同的大小 ( 或 ROI 大小 ) Min 查找两个数组元素之间的较小值 void cvmin( const CvArr* src1, const CvArr* src2, CvArr* dst ); src1 src2 dst 第一个原数组 第二个原数组. 输出数组. 函数 cvmin 计算两个数组元素的较小值 dst(i)=min(src1(i),src2(i)) 所有数组必须有一个单一通道, 相同的数据类型相同的大小 ( 或 ROI 大小 ) MinS 查找数组元素和数量之间的较小值 void cvmins( const CvArr* src, double value, CvArr* dst ); src 第一个原数组 107

108 value dst 数量值. 输出数组.. 函数 cvmins 计算数组元素和数量之量的较小值 : dst(i)=min(src(i), value) 所有数组必须有一个单一通道, 相同的数据类型相同的大小 ( 或 ROI 大小 ) AbsDiff 计算两个数组差的绝对值 void cvabsdiff( const CvArr* src1, const CvArr* src2, CvArr* dst ); src1 src2 dst 第一个原数组 第二个原数组 输出数组 函数 cvabsdiff 计算两个数组差的绝对值 dst(i)c = abs(src1(i)c - src2(i)c). 所有数组必须有相同的数据类型相同的大小 ( 或 ROI 大小 ) AbsDiffS 计算数组元素与数量之间差的绝对值 void cvabsdiffs( const CvArr* src, CvArr* dst, CvScalar value ); #define cvabs(src, dst) cvabsdiffs(src, dst, cvscalarall(0)) src dst value 原数组. 输出数组 数量. 函数 cvabsdiffs 计算数组元素与数量之间差的绝对值 108

109 dst(i)c = abs(src(i)c - valuec). 所有数组必须有相同的数据类型相同的大小 ( 或 ROI 大小 ) 统计 CountNonZero 计算非零数组元素个数 int cvcountnonzero( const CvArr* arr ); arr 数组, 必须是单通道数组或者设置 COI( 感兴趣通道 ) 的多通道图像 函数 cvcountnonzero 返回 arr 中非零元素的数目 : result = sumi arr(i)!=0 当 IplImage 支持 ROI 和 COI Sum 计算数组元素的和 CvScalar cvsum( const CvArr* arr ); arr 数组. 函数 cvsum 独立地为每一个通道计算数组元素的和 S : Sc = sumi arr(i)c 如果数组是 IplImage 类型和设置了 COI, 该函数只处理选定的通道并将和存储到第一个标量成员 (S0) 常见论坛讨论贴 cvsum 的结果分析 Avg 计算数组元素的平均值 CvScalar cvavg( const CvArr* arr, const CvArr* mask=null ); arr mask 数组. 可选操作掩模 109

110 函数 cvavg 独立地为每一个通道计算数组元素的平均值 M : 如果数组是 IplImage 类型和设置 COI, 该函数只处理选定的通道并将和存储到第一个标量成员 (S0) AvgSdv 计算数组元素的平均值和标准差 void cvavgsdv( const CvArr* arr, CvScalar* mean, CvScalar* std_dev, const CvArr* mask=null ); arr 数组 mean 指向平均值的指针, 如果不需要的话可以为空 ( NULL) std_dev 指向标准差的指针 mask 可选操作掩模 函数 cvavgsdv 独立地为每一个通道计算数组元素的平均值和标准差 : 如果数组是 IplImage 类型和设置了 COI, 该函数只处理选定的通道并将平均值和标准差存储到第一个输出标量成员 (mean 0 和 std-dev 0 ) MinMaxLoc 查找数组和子数组的全局最小值和最大值 110

111 void cvminmaxloc( const CvArr* arr, double* min_val, double* max_val, mask=null ); CvPoint* min_loc=null, CvPoint* max_loc=null, const CvArr* arr 输入数组, 单通道或者设置了 COI 的多通道 min_val 指向返回的最小值的指针 max_val 指向返回的最大值的指针 min_loc 指向返回的最小值的位置指针 max_loc 指向返回的最大值的位置指针 mask 选择一个子数组的操作掩模 函数 MinMaxLoc 查找元素中的最小值和最大值以及他们的位置 函数在整个数组 或选定的 ROI 区域 ( 对 IplImage) 或当 MASK 不为 NULL 时指定的数组区域中, 搜索极值 如果数组不止一个通道, 它就必须是设置了 COI 的 IplImage 类型 如果是多维数组 min_loc->x 和 max_loc->x 将包含极值的原始位置信息 ( 线性的 ) Norm 计算数组的绝对范数, 绝对差分范数或者相对差分范数 double cvnorm( const CvArr* arr1, const CvArr* arr2=null, int norm_type=cv_l2, const CvArr* mask=null ); arr1 第一输入图像 arr2 第二输入图像, 如果为空 (NULL), 计算 arr1 的绝对范数, 否则计算 arr1-arr2 的绝对范数或者相对范数 normtype 范数类型, 参见 讨论 mask 可选操作掩模 如果 arr2 为空 (NULL), 函数 cvnorm 计算 arr1 的绝对范数 : norm = arr1 C = maxi abs(arr1(i)), 如果 normtype = CV_C norm = arr1 L1 = sumi abs(arr1(i)), 如果 normtype = CV_L1 norm = arr1 L2 = sqrt( sumi arr1(i)2), 如果 normtype = CV_L2 111

112 如果 arr2 不为空 (NULL), 该函数计算绝对差分范数或者相对差分范数 : norm = arr1-arr2 C = maxi abs(arr1(i)-arr2(i)), 如果 normtype = CV_C norm = arr1-arr2 L1 = sumi abs(arr1(i)-arr2(i)), 如果 normtype = CV_L1 norm = arr1-arr2 L2 = sqrt( sumi (arr1(i)-arr2(i))2 ), 如果 normtype = CV_L2 或者 norm = arr1-arr2 C/ arr2 C, 如果 normtype = CV_RELATIVE_C norm = arr1-arr2 L1/ arr2 L1, 如果 normtype = CV_RELATIVE_L1 norm = arr1-arr2 L2/ arr2 L2, 如果 normtype = CV_RELATIVE_L2 函数 Norm 返回计算所得的范数 多通道数组被视为单通道处理, 因此, 所有通道的结果是结合在一起的 Reduce 简化一个矩阵成为一个向量 cvreduce( const CvArr* src, CvArr* dst, int dim, int op=cv_reduce_sum); src dst dim op 输入矩阵输出的通过处理输入矩阵的所有行 / 列而得到的单行 / 列向量矩阵被简化后的维数索引.0 意味着矩阵被处理成一行,1 意味着矩阵被处理成为一列,-1 时维数将根据输出向量的大小自动选择. 简化操作的方式, 可以有以下几种取值 : CV_REDUCE_SUM- 输出是矩阵的所有行 / 列的和. CV_REDUCE_AVG- 输出是矩阵的所有行 / 列的平均向量. CV_REDUCE_MAX- 输出是矩阵的所有行 / 列的最大值. CV_REDUCE_MIN- 输出是矩阵的所有行 / 列的最小值. 这个函数通过把矩阵的每行 / 列当作一维向量并对其做某种特殊的操作将一个矩阵简化成为一个向量. 例如, 这个函数可以用于计算一个光栅图象的水平或者垂直投影. 在取值为 CV_REDUCE_AVG 与 CV_REDUCE_SUM 的情况下输出可能有很大的位深度用于维持准确性, 这两种方式也适合于处理多通道数组. 注意, 对于 CV_REDUCE_SUM 和 CV_REDUCE_AVG 方式来说, 输入和输出的位数定义有如下关系输入 :CV_8U 输出 :CV_32S CV_32F 输入 :CV_16U 输出 :CV_32F CV_64F 输入 :CV_16S 输出 :CV_32F CV_64F 112

113 输入 :CV_32F 输出 : CV_32F CV_64F 输入 :CV_64F 输出 : CV_64F 而对于 CV_REDUCE_MAX 和 CV_REDUCE_MIN 方式来说, 输入和输出的位数必须一致线性代数 DotProduct 用欧几里得准则计算两个数组的点积 double cvdotproduct( const CvArr* src1, const CvArr* src2 ); src1 src2 第一输入数组 第二输入数组 函数 cvdotproduct 计算并返回两个数组的欧几里得点积 src1 src2 = sumi(src1(i)*src2(i)) 如果是多通道数组, 所有通道的结果是累加在一起的 特别地, cvdotproduct(a,a), 将返回 a 2, 这里 a 是一个复向量 该函数可以处理多通道数组, 逐行或逐层等等 Normalize 根据某种范数或者数值范围归一化数组. void cvnormalize( const CvArr* src, CvArr* dst, double a=1, double b=0, int norm_type=cv_l2, const CvArr* mask=null ); src 输入数组 dst 输出数组, 支持原地运算 a 输出数组的最小 / 最大值或者输出数组的范数 b 输出数组的最大 / 最小值 norm_type 归一化的类型, 可以有以下的取值 : CV_C - 归一化数组的 C- 范数 ( 绝对值的最大值 ) CV_L1 - 归一化数组的 L1- 范数 ( 绝对值的和 ) CV_L2 - 归一化数组的 ( 欧几里德 )L2- 范数 CV_MINMAX - 数组的数值被平移或缩放到一个指定的范围 113

114 mask 操作掩膜, 用于指示函数是否仅仅对指定的元素进行操作 该函数归一化输入数组使它的范数或者数值范围在一定的范围内当 norm_type==cv_minmax: dst(i,j)=(src(i,j)-min(src))*(b'-a')/(max(src)-min(src)) + a', if mask(i,j)!=0 dst(i,j)=src(i,j) otherwise 其中 b'=max(a,b), a'=min(a,b); 当 norm_type!=cv_minmax: dst(i,j)=src(i,j)*a/cvnorm(src,0,norm_type,mask), if mask(i,j)!=0 dst(i,j)=src(i,j) otherwise 下面是一个简单的例子 : float v[3] = 1, 2, 3 }; CvMat V = cvmat( 1, 3, CV_32F, v ); // make vector v unit-length; // equivalent to // for(int i=0;i<3;i++) v[i]/=sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); cvnormalize( &V, &V ); CrossProduct 计算两个三维向量的叉积 void cvcrossproduct( const CvArr* src1, const CvArr* src2, CvArr* dst ); src1 src2 dst 第一输入向量 第二输入向量 输出向量 函数 cvcrossproduct 计算两个三维向量的差积 : dst = src1 src2, (dst1 = src12src23 - src13src22, dst2 = src13src21 - src11src23, dst3 = src11src22 - src12src21). ScaleAdd 计算一个数组缩放后与另一个数组的和 114

115 void cvscaleadd( const CvArr* src1, CvScalar scale, const CvArr* src2, CvArr* dst ); #define cvmuladds cvscaleadd src1 scale src2 dst 第一输入数组第一输入数组的缩放因子第二输入数组输出数组 函数 cvscaleadd 计算一个数组缩放后与另一个数组的和 : dst(i)=src1(i)*scale + src2(i) 所有的数组参数必须有相同的类型和大小 GEMM 通用矩阵乘法 void cvgemm( const CvArr* src1, const CvArr* src2, double alpha, const CvArr* src3, double beta, CvArr* dst, int tabc=0 ); #define cvmatmuladd( src1, src2, src3, dst ) cvgemm( src1, src2, 1, src3, 1, dst, 0 ) #define cvmatmul( src1, src2, dst ) cvmatmuladd( src1, src2, 0, dst ) src1 src2 src3 dst tabc 第一输入数组第二输入数组第三输入数组 ( 偏移量 ), 如果没有偏移量, 可以为空 ( NULL) 输出数组 T 操作标志, 可以是 0 或者下面列举的值的组合 : CV_GEMM_A_T - 转置 src1 CV_GEMM_B_T - 转置 src2 CV_GEMM_C_T - 转置 src3 例如, CV_GEMM_A_T+CV_GEMM_C_T 对应 alpha*src1t*src2 + beta*src3t 115

116 函数 cvgemm 执行通用矩阵乘法 : dst = alpha*op(src1)*op(src2) + beta*op(src3), 这里 op(x) 是 X 或者 XT 所有的矩阵应该有相同的数据类型和协调的矩阵大小 支持实数浮点矩阵或者复数浮点矩阵 Transform 对数组每一个元素执行矩阵变换 void cvtransform( const CvArr* src, CvArr* dst, const CvMat* transmat, const CvMat* shiftvec=null ); src 输入数组 dst 输出数组 transmat 变换矩阵 shiftvec 可选偏移向量函数 cvtransform 对数组 src 每一个元素执行矩阵变换并将结果存储到 dst: dst(i)=transmat*src(i) + shiftvec 或者 dst(i)k=sumj(transmat(k,j)*src(i)j) + shiftvec(k) N- 通道数组 src 的每一个元素都被视为一个 N 元向量, 使用一个 M N 的变换矩阵 transmat 和偏移向量 shiftvec 把它变换到一个 M- 通道的数组 dst 的一个元素中 这里可以选择将偏移向量 shiftvec 嵌入到 transmat 中 这样的话 transmat 应该是 M N+1 的矩阵, 并且最右边的一列被看作是偏移向量 输入数组和输出数组应该有相同的位深 (depth) 和同样的大小或者 ROI 大小 transmat 和 shiftvec 应该是实数浮点矩阵 该函数可以用来进行 ND 点集的几何变换, 任意的线性颜色空间变换, 通道转换等 PerspectiveTransform 向量数组的透视变换 void cvperspectivetransform( const CvArr* src, CvArr* dst, const CvMat* mat ); src 输入的三通道浮点数组 116

117 dst mat 输出三通道浮点数组 4 4 变换矩阵 函数 cvperspectivetransform 用下面的方式变换 src 的每一个元素 ( 通过将其视为二维或者三维的向量 ): (x, y, z) -> (x'/w, y'/w, z'/w) 或者 (x, y) -> (x'/w, y'/w), 这里 (x', y', z', w') = mat*(x, y, z, 1) 或者 (x', y', w') = mat*(x, y, 1) 并且 w = w' 如果 w'!=0, 否则 w = inf MulTransposed 计算数组和数组的转置的乘积 void cvmultransposed( const CvArr* src, CvArr* dst, int order, const CvArr* delta=null ); src dst order delta 输入矩阵目标矩阵乘法顺序一个可选数组, 在乘法之前从 src 中减去该数组 函数 cvmultransposed 计算 src 和它的转置的乘积 函数求值公式 : 如果 order=0 dst=(src-delta)*(src-delta)t 否则 Trace dst=(src-delta)t*(src-delta) 返回矩阵的迹 117

118 CvScalar cvtrace( const CvArr* mat ); mat 输入矩阵 函数 cvtrace 返回矩阵 mat 的对角线元素的和 tr(src) = mat(i,i) Transpose i 矩阵的转置 void cvtranspose( const CvArr* src, CvArr* dst ); #define cvt cvtranspose src dst 输入矩阵 目标矩阵 函数 cvtranspose 对矩阵 src 求转置 : dst(i,j)=src(j,i) 注意, 假设是复数矩阵不会求得复数的共轭 共轭应该是独立的 : 查看的 cvxors 例子代码 Det 返回矩阵的行列式值 double cvdet( const CvArr* mat ); mat 输入矩阵 函数 cvdet 返回方阵 mat 的行列式值 对小矩阵直接计算, 对大矩阵用高斯 (GAUSSIAN) 消去法 对于对称正定 (positive-determined) 矩阵也可以用 SVD 函数来求,U=V=NULL, 然后用 w 的对角线元素的乘积来计算行列式 Invert 查找矩阵的逆矩阵或伪逆矩阵 double cvinvert( const CvArr* src, CvArr* dst, int method=cv_lu ); #define cvinv cvinvert src 118

119 dst method 输入矩阵目标矩阵求逆方法 : CV_LU - 最佳主元选取的高斯消除法 CV_SVD - 奇异值分解法 (SVD) CV_SVD_SYM - 正定对称矩阵的 SVD 方法 函数 cvinvert 对矩阵 src 求逆并将结果存储到 dst 如果是 LU 方法该函数返回 src 的行列式值 (src 必须是方阵 ) 如果是 0, 矩阵不求逆, dst 用 0 填充 如果 SVD 方法该函数返回 src 的条件数的倒数 ( 最小奇异值和最大奇异值的比值 ), 如果 src 全为 0 则返回 0 如果 src 是奇异的, SVD 方法计算一个伪逆矩阵 Solve 求解线性系统或者最小二乘法问题 int cvsolve( const CvArr* src1, const CvArr* src2, CvArr* dst, int method=cv_lu ); src1 输入矩阵 src2 线性系统的右部 dst 输出解答 method 解决方法 ( 矩阵求逆 ) : CV_LU - 最佳主元选取的高斯消除法 CV_SVD - 奇异值分解法 (SVD) CV_SVD_SYM - 对正定对称矩阵的 SVD 方法函数 cvsolve 解决线性系统或者最小二乘法问题 ( 后者用 SVD 方法可以解决 ): 如果使用 CV_LU 方法 如果 src1 是非奇异的, 该函数则返回 1, 否则返回 0, 在后一种情况下 dst 是无效的 SVD 对实数浮点矩阵进行奇异值分解 void cvsvd( CvArr* A, CvArr* W, CvArr* U=NULL, CvArr* V=NULL, int flags=0 ); A 119

120 W U V flags M N 的输入矩阵结果奇异值矩阵 (M N 或者 N N) 或者向量 (N 1). 可选的左部正交矩阵 (M M or M N). 如果 CV_SVD_U_T 被指定, 应该交换上面所说的行与列的数目 可选右部正交矩阵 (N N) 操作标志 ; 可以是 0 或者下面的值的组合 : CV_SVD_MODIFY_A 通过操作可以修改矩阵 src1 这样处理速度会比较快 CV_SVD_U_T 意味着会返回转置矩阵 U, 指定这个标志将加快处理速度 CV_SVD_V_T 意味着会返回转置矩阵 V, 指定这个标志将加快处理速度 函数 cvsvd 将矩阵 A 分解成一个对角线矩阵和两个正交矩阵的乘积 : 这里 W 是一个奇异值的对角线矩阵, 它可以被编码成奇异值的一维向量,U 和 V 也是一样 所有的奇异值都是非负的并按降序存储 (U 和 V 也相应的存储 ) SVD 算法在数值处理上已经很稳定, 它的典型应用包括 : 当 A 是一个方阵 对称阵和正矩阵时精确的求解特征值问题, 例如, 当 A 时一个协方差矩阵时 在这种情况下 W 将是一个特征值的的向量, 并且 U=V 是矩阵的特征向量 ( 因此, 当需要计算特征向量时 U 和 V 只需要计算其中一个就可以了 ) 精确的求解病态线性系统 超定线性系统的最小二乘求解 上一个问题和这个问题都可以用指定 CV_SVD 的 cvsolve 方法 精确计算矩阵的不同特征, 如秩 ( 非零奇异值的数目 ), 条件数 ( 最大奇异值和最小奇异值的比例 ), 行列式值 ( 行列式的绝对值等于奇异值的乘积 ). 上述的所有这些值都不要求计算矩阵 U 和 V SVBkSb 奇异值回代算法 (back substitution) void cvsvbksb( const CvArr* W, const CvArr* U, const CvArr* V, const CvArr* B, CvArr* X, int flags ); W U 奇异值矩阵或者向量 左正交矩阵 ( 可能是转置的 ) 120

121 V B X flags 右正交矩阵 ( 可能是转置的 ) 原始矩阵 A 的伪逆的乘法矩阵 这个是可选参数 如果它被省略则假定它是一个适当大小的单位矩阵 ( 因此 x 将是 A 的伪逆的重建 ). 目标矩阵 : 奇异值回代算法的结果操作标志, 和刚刚讨论的 cvsvd 的标志一样 函数 cvsvbksb 为被分解的矩阵 A 和矩阵 B 计算回代逆 (back substitution) ( 参见 cvsvd 说明 ) : X=V*W-1*UT*B 这里 W-1(i,i)=1/W(i,i) 如果 W(i,i) > epsilon sumiw(i,i), 否则 :0. epsilon 是一个依赖于矩阵数据类型的的很小的数 该函数和 cvsvd 函数被用来执行 cvinvert 和 cvsolve, 用这些函数 (svd & bksb) 的原因是初级函数 (low-level) 函数可以避免高级函数 (inv & solve) 计算中内部分配的临时矩阵 EigenVV 计算对称矩阵的特征值和特征向量 void cveigenvv( CvArr* mat, CvArr* evects, CvArr* evals, double eps=0 ); mat evects evals eps 输入对称方阵 在处理过程中将被改变 特征向量输出矩阵, 连续按行存储 特征值输出矩阵, 按降序存储 ( 当然特征值和特征向量的排序是同步的 ) 对角化的精确度 ( 典型地, DBL_EPSILON= 就足够了 ) 函数 cveigenvv 计算矩阵 A 的特征值和特征向量 : mat*evects(i,:)' = evals(i)*evects(i,:)' ( 在 MATLAB 的记法 ) 矩阵 A 的数据将会被这个函数修改 目前这个函数比函数 cvsvd 要慢, 精确度要低, 如果已知 A 是正定的,( 例如, 它是一个协方差矩阵 ), 它通常被交给函数 cvsvd 来计算其特征值和特征向量, 尤其是在不需要计算特征向量的情况下 121

122 CalcCovarMatrix 计算向量集合的协方差矩阵 void cvcalccovarmatrix( const CvArr** vects, int count, CvArr* cov_mat, CvArr* avg, int flags ); vects 输入向量 他们必须有同样的数据类型和大小 这个向量不一定非是一维的, 他们也可以是二维 ( 例如, 图像 ) 等等 count 输入向量的数目 cov_mat 输出协方差矩阵, 它是浮点型的方阵 avg 输入或者输出数组 ( 依赖于标记 flags ) - 输入向量的平均向量 flags 操作标志, 下面值的组合 : CV_COVAR_SCRAMBLED - 输出协方差矩阵按下面计算 : scale * [vects[0] avg,vects[1] avg,...] T * [vects[0] avg,vects[1] avg,...], 即协方差矩阵是 count count. 这样一个不寻常的矩阵用于一组大型向量的快速 PCA 方法 ( 例如, 人脸识别的 EigenFaces 技术 ) 这个混杂("scrambled") 矩阵的特征值将和真正的协方差矩阵的特征值匹配, 真正的特征向量可以很容易的从混杂 ("scrambled") 协方差矩阵的特征向量中计算出来 CV_COVAR_NORMAL - 输出协方差矩阵被计算成 : scale * [vects[0] avg,vects[1] avg,...] * [vects[0] avg,vects[1] avg,...] T, 也就是说, cov_mat 将是一个和每一个输入向量的元素数目具有同样线性大小的通常协方差矩阵 CV_COVAR_SCRAMBLED 和 CV_COVAR_NORMAL 只能同时指定其中一个 CV_COVAR_USE_AVG - 如果这个标志被指定, 该函数将不会从输入向量中计算 avg, 而是用过去的 avg 向量, 如果 avg 已经以某种方式计算出来了这样做是很有用的 或者如果协方差矩阵是部分计算出来的 - 倘若这样, avg 不是输入向量的子集的平均值, 而是整个集合的平均向量 CV_COVAR_SCALE - 如果这个标志被指定, 协方差矩阵被缩放了 the covariation matrix is scaled. 在 "normal" 模式下缩放比例是 1./count, 在 "scrambled" 模式下缩放比例是每一个输入向量的元素总和的倒数 缺省地 ( 如果没有指定标志 ) 协方差矩阵不被缩放 (scale=1) 函数 cvcalccovarmatrix 计算输入向量的协方差矩阵和平均向量 该函数可以被运用到主成分分析中 (PCA), 以及马氏距离 (Mahalanobis distance) 比较向量中等等 Mahalanobis 计算两个向量之间的马氏距离 (Mahalanobis distance) double cvmahalanobis( const CvArr* vec1, const CvArr* vec2, CvArr* mat ); 122

123 vec1 第一个一维输入向量 vec2 第二个一维输入向量 mat 协方差矩阵的逆矩阵函数 cvmahalanobis 计算两个向量之间的加权距离, 其返回结果是 : 协方差矩阵可以用函数 cvcalccovarmatrix 计算出来, 逆矩阵可以用函数 cvinvert 计算出来 (CV_SVD 方法是一个比较好的选择, 因为矩阵可能是奇异的 ). CalcPCA 对一个向量集做 PCA 变换 void cvcalcpca( const CvArr* data, CvArr* avg, CvArr* eigenvalues, CvArr* eigenvectors, int flags ); data 输入数据, 每个向量是单行向量 (CV_PCA_DATA_AS_ROW) 或者单列向量 (CV_PCA_DATA_AS_COL). avg 平均向量, 在函数内部计算或者由调用者提供 eigenvalues 输出的协方差矩阵的特征值 eigenvectors 输出的协方差矩阵的特征向量 ( 也就是主分量 ), 每个向量一行 flags 操作标志, 可以是以下几种方式的组合 : CV_PCA_DATA_AS_ROW - 向量以行的方式存放 ( 也就是说任何一个向量都是连续存放的 ) CV_PCA_DATA_AS_COL - 向量以列的方式存放 ( 也就是说某一个向量成分的数值是连续存放的 ) ( 上面两种标志是互相排斥的 ) CV_PCA_USE_AVG - 使用预先计算好的平均值该函数对某个向量集做 PCA 变换. 它首先利用 cvcalccovarmatrix 计算协方差矩阵然后计算协方差矩阵的特征值与特征向量. 输出的特征值 / 特征向量的个数小于或者等于 MIN(rows(data),cols(data)). ProjectPCA 把向量向某个子空间投影 123

124 void cvprojectpca( const CvArr* data, const CvArr* avg, const CvArr* eigenvectors, CvArr* result ) data 输入数据, 每个向量可以是单行或者单列 avg 平均向量. 要么它是单行向量那么意味着输入数据以行数据的形式存放, 要么就是单列向量, 那么就意味着那么输入向量就是以列的方式存放. eigenvectors 特征向量 ( 主分量 ), 每个向量一行. result 输出的分解系数矩阵, 矩阵的行数必须与输入向量的个数相等, 矩阵的列数必须小于特征向量的行数. 该函数将输入向量向一个正交系 (eigenvectors) 投影. 在计算点乘之前, 输入向量要减去平均向量 : result(i,:)=(data(i,:)-avg)*eigenvectors' // for CV_PCA_DATA_AS_ROW layout. BackProjectPCA 根据投影系数重构原来的向量 void cvbackprojectpca( const CvArr* proj, const CvArr* avg, const CvArr* eigenvects, CvArr* result ); proj 输入数据, 与 cvprojectpca 里面的格式一致 avg 平均向量. 如果它是单行向量, 那么意味着输出向量是以行的方式存放. 否则就是单列向量, 那么输出向量就是以列的方式存放. eigenvectors 特征向量 ( 主分量 ), 每个向量一行. result 输出的重构出来的矩阵该函数根据投影系数重构原来的向量 : result(i,:)=proj(i,:)*eigenvectors + avg // for CV_PCA_DATA_AS_ROW layout. 数学函数 Round, Floor, Ceil 转换浮点数为整数 int cvround( double value ); int cvfloor( double value ); 124

125 int cvceil( double value ); value 输入浮点值 函数 cvround, cvfloor, cvceil 用一种舍入方法将输入浮点数转换成整数 cvround 返回和参数最接近的整数值 cvfloor 返回不大于参数的最大整数值 cvceil 返回不小于参数的最小整数值 在某些体系结构中该函数工作起来比标准 C 操作起来还要快 如果参数的绝对值大于 2 31, 结果是不可预料的 对特殊值 (±Inf, NaN) 未进行处理 Sqrt 计算平方根 float cvsqrt( float value ); value 输入浮点值 函数 cvsqrt 计算输入值的平方根 如果输入的是复数, 结果将不可预料 InvSqrt 计算平方根的倒数 float cvinvsqrt( float value ); value 输入浮点值 函数 cvinvsqrt 计算输入值的平方根的倒数, 大多数情况下它比 1./sqrt(value) 要快 如果输入的是 0 或者复数, 结果将不可预料 特别值 (±Inf, NaN) 是不可控制的 Cbrt 计算立方根 float cvcbrt( float value ); value 输入浮点值 函数 cvcbrt 计算输入值的立方根, 大多数情况下它比 pow(value,1./3) 要快 另外, 负数也是可操作的 特别值 (±Inf, NaN) 是不可控制的 FastArctan 计算二维向量的角度 125

126 float cvfastarctan( float y, float x ); x y 二维向量的 x 坐标 二维向量的 y 坐标 函数 cvfastarctan 计算二维向量的全范围角度角度, 变化范围是 0 到 360 精确度为 ~0.1 IsNaN 判断输入是否是一个数字 int cvisnan( double value ); value 输入浮点值 函数 cvisnan 发现输入是一个数字则返回 1 ( IEEE754 标准 ), 否则返回 0 IsInf 判断输入是否是无穷大 int cvisinf( double value ); value 输入浮点值 函数 cvisinf 如果输入是 ±Infinity ( IEEE754 标准 ) 则返回 1, 否则返回 0. CartToPolar 计算二维向量的长度和 / 或者角度 void cvcarttopolar( const CvArr* x, const CvArr* y, CvArr* magnitude, CvArr* angle=null, int angle_in_degrees=0 ); x x 坐标数组 y y 坐标数组 magnitude 存储向量长度输出数组, 如果不是必要的它可以为空 (NULL) angle 126

127 存储角度输出数组, 如果不是必要的它可以为空 (NULL) 它可以被标准化为弧度 (0..2π) 或者度数 ( ) 所有的数组只支持浮点类型的运算, 也即 x,y,magnitude,angle 必须是浮点类型的数组 angle_in_degrees 指示角度是用弧度或者度数表示的标志, 缺省模式为弧度函数 cvcarttopolar 计算二维向量 (x(i),y(i)) 的长度, 角度, 或者两者同时计算 : magnitude(i) = sqrt(x(i) 2 + y(i) 2 ), angle(i) = atan(y(i) / x(i)) 角度的精确度 0.1. (0,0) 点的角度被设置为 0. ( 建议 : 英文文档虽然是写成 atan( y(i)/x(i) ), 但是建议和 C 中的表达方式统一 atan 不能识别在那个象限, 只能返回 0-180,atan2(x,y) 才能返回 的值 ) PolarToCart 计算极坐标形式的二维向量对应的直角坐标 void cvpolartocart( const CvArr* magnitude, const CvArr* angle, CvArr* x, CvArr* y, int angle_in_degrees=0 ); magnitude 长度数组. 如果为空 (NULL), 长度被假定为全是 1's. angle 角度数组, 弧度或者角度表示. x 输出 x 坐标数组, 如果不需要, 可以为空 (NULL). y 输出 y 坐标数组, 如果不需要, 可以为空 (NULL). angle_in_degrees 指示角度是用弧度或者度数表示的标志, 缺省模式为弧度函数 cvpolartocart 计算每个向量 magnitude(i)*exp(angle(i)*j), j=sqrt(-1) 的 x 坐标,y 坐标或者两者都计算 : x(i)=magnitude(i)*cos(angle(i)), y(i)=magnitude(i)*sin(angle(i)) Pow 对数组内每个元素求幂 127

128 void cvpow( const CvArr* src, CvArr* dst, double power ); src dst power 输入数组 输出数组, 应该和输入数组有相同的类型 幂指数 函数 cvpow 计算输入数组的每个元素的 p 次幂 : dst(i)=src(i)^p, 如果 p 是整数否则 dst(i)=abs(src(i))^p 也就是说, 对于非整型的幂指数使用输入数组元素的绝对值进行计算 然而, 使用一些额外的操作, 负值也可以得到正确的结果, 象下面的例子, 计算数组元素的立方根 : CvSize size = cvgetsize(src); CvMat* mask = cvcreatemat( size.height, size.width, CV_8UC1 ); cvcmps( src, 0, mask, CV_CMP_LT ); /* 查找负数 */ cvpow( src, dst, 1./3 ); cvsubrs( dst, cvscalarall(0), dst, mask ); /* 输入的负值的结果求反 */ cvreleasemat( &mask ); 对于一些幂值, 例如整数值, 0.5 和 -0.5, 优化算法被使用 Exp 计算数组元素的指数幂 void cvexp( const CvArr* src, CvArr* dst ); src dst 输入数组 输出数组, 它应该是 double 型的或者和输入数组有相同的类型 函数 cvexp 计算输入数组的每个元素的 e 次幂 : dst(i)=exp(src(i)) 最大相对误差为 7e-6. 通常, 该函数转换无法输出的值为 0 输出 Log 128

129 计算每个数组元素的绝对值的自然对数 void cvlog( const CvArr* src, CvArr* dst ); src dst 输入数组 输出数组, 它应该是 double 型的或者和输入数组有相同的类型 函数 cvlog 计算输入数组每个元素的绝对值的自然对数 : dst(i)=log(abs(src(i))), src(i)!=0 dst(i)=c, src(i)=0 这里 C 是一个大负数 ( -700 现在的实现中 ) SolveCubic 求解曲线函数的实根 void cvsolvecubic( const CvArr* coeffs, CvArr* roots ); coeffs roots 等式系数, 一个三到四个元素的数组. 输出的矩阵等式的实根 它应该具有三个元素. 函数 cvsolvecubic 求解曲线函数的实根 : coeffs[0]*x^3 + coeffs[1]*x^2 + coeffs[2]*x + coeffs[3] = 0 ( 如果 coeffs 是四元素的矢量 ) 或者 x^3 + coeffs[0]*x^2 + coeffs[1]*x + coeffs[2] = 0 ( 如果 coeffs 是三元素的矢量 ) 函数返回求解得到的实根数目. 实根被存储在矩阵 root 中, 如果只有一个实根则用 0 来替代相关值. 随机数生成 RNG 初始化随机数生成器状态 CvRNG cvrng( int64 seed=-1 ); seed 64-bit 的值用来初始化一个随机序列 129

130 函数 cvrng 初始化随机数生成器并返回其状态 指向这个状态的指针可以传递给函数 cvrandint, cvrandreal 和 cvrandarr. 在通常的实现中使用一个 multiply-with-carry generator RandArr 用随机数填充数组并更新 RNG 状态 void cvrandarr( CvRNG* rng, CvArr* arr, int dist_type, CvScalar param1, CvScalar param2 ); rng 被 cvrng 初始化的 RNG 状态. arr 输出数组 dist_type 分布类型 : CV_RAND_UNI - 均匀分布 CV_RAND_NORMAL - 正态分布或者高斯分布 param1 分布的第一个参数 如果是均匀分布它是随机数范围的闭下边界 如果是正态分布它是随机数的平均值 param2 分布的第二个参数 如果是均匀分布它是随机数范围的开上边界 如果是正态分布它是随机数的标准差 函数 cvrandarr 用均匀分布的或者正态分布的随机数填充输出数组 在下面的例子中该函数被用来添加一些正态分布的浮点数到二维数组的随机位置 /* let's noisy_screen be the floating-point 2d array that is to be "crapped" */ CvRNG rng_state = cvrng(0xffffffff); int i, pointcount = 1000; /* allocate the array of coordinates of points */ CvMat* locations = cvcreatemat( pointcount, 1, CV_32SC2 ); /* arr of random point values */ CvMat* values = cvcreatemat( pointcount, 1, CV_32FC1 ); CvSize size = cvgetsize( noisy_screen ); cvrandinit( &rng_state, 0, 1, /* 现在使用虚参数以后再调整 */ 0xffffffff /* 这里使用一个确定的种子 */, CV_RAND_UNI /* 指定为均匀分布类型 */ ); 130

131 /* 初始化 locations */ cvrandarr( &rng_state, locations, CV_RAND_UNI, cvscalar(0,0,0,0), cvscalar(size.width,size.height,0,0) ); /* modify RNG to make it produce normally distributed values */ rng_state.disttype = CV_RAND_NORMAL; cvrandsetrange( &rng_state, 30 /* deviation */, 100 /* average point brightness */, -1 /* initialize all the dimensions */ ); /* generate values */ cvrandarr( &rng_state, values, CV_RAND_NORMAL, cvrealscalar(100), // average intensity cvrealscalar(30) // deviation of the intensity ); /* set the points */ for( i = 0; i < pointcount; i++ ) CvPoint pt = *(CvPoint*)cvPtr1D( locations, i, 0 ); float value = *(float*)cvptr1d( values, i, 0 ); *((float*)cvptr2d( noisy_screen, pt.y, pt.x, 0 )) += value; } /* not to forget to release the temporary arrays */ cvreleasemat( &locations ); cvreleasemat( &values ); /* RNG state does not need to be deallocated */ RandInt 返回 32-bit 无符号整型并更新 RNG unsigned cvrandint( CvRNG* rng ); rng 被 cvrng 初始化的 RNG 状态, 被 RandSetRange ( 虽然, 后面这个函数对我们正讨论的函数的结果没有什么影响 ) 随意地设置 函数 cvrandint 返回均匀分布的随机 32-bit 无符号整型值并更新 RNG 状态 它和 C 运行库里面的 rand() 函数十分相似, 但是它产生的总是一个 32-bit 数而 rand() 返回一个 0 到 RAND_MAX ( 它是 2**16 或者 2**32, 依赖于操作平台 ) 之间的数 131

132 该函数用来产生一个标量随机数, 例如点, patch sizes, table indices 等, 用模操作可以产生一个确定边界的整数, 人和其他特定的边界缩放到 可以产生一个浮点数 下面是用 cvrandint 重写的前一个函数讨论的例子 : /* the input and the task is the same as in the previous sample. */ CvRNG rng_state = cvrng(0xffffffff); int i, pointcount = 1000; /*... - no arrays are allocated here */ CvSize size = cvgetsize( noisy_screen ); /* make a buffer for normally distributed numbers to reduce call overhead */ #define buffersize 16 float normalvaluebuffer[buffersize]; CvMat normalvaluemat = cvmat( buffersize, 1, CV_32F, normalvaluebuffer ); int valuesleft = 0; for( i = 0; i < pointcount; i++ ) CvPoint pt; /* generate random point */ pt.x = cvrandint( &rng_state ) % size.width; pt.y = cvrandint( &rng_state ) % size.height; if( valuesleft <= 0 ) /* fulfill the buffer with normally distributed numbers if the buffer is empty */ cvrandarr( &rng_state, &normalvaluemat, CV_RAND_NORMAL, cvrealscalar(100), cvrealscalar(30) ); valuesleft = buffersize; } ((float*)cvptr2d( noisy_screen, pt.y, pt.x, 0 ) = normalvaluebuffer[--valuesleft]; } /* there is no need to deallocate normalvaluemat because we have both the matrix header and the data on stack. It is a common and efficient practice of working with small, fixed-size matrices */ RandReal 返回浮点型随机数并更新 RNG 132

133 double cvrandreal( CvRNG* rng ); rng 被 cvrng 初始化的 RNG 状态 函数 cvrandreal 返回均匀分布的随机浮点数, 范围在 0..1 之间 ( 不包括 1 ) 离散变换 DFT 执行一维或者二维浮点数组的离散傅立叶正变换或者离散傅立叶逆变换 #define CV_DXT_FORWARD 0 #define CV_DXT_INVERSE 1 #define CV_DXT_SCALE:2 #define CV_DXT_ROWS: 4 #define CV_DXT_INV_SCALE (CV_DXT_SCALE CV_DXT_INVERSE) #define CV_DXT_INVERSE_SCALE CV_DXT_INV_SCALE void cvdft( const CvArr* src, CvArr* dst, int flags, int nonzero_rows=0); src 输入数组, 实数或者复数. dst 输出数组, 和输入数组有相同的类型和大小 flags 变换标志, 下面的值的组合 : CV_DXT_FORWARD - 正向 1D 或者 2D 变换. 结果不被缩放. CV_DXT_INVERSE - 逆向 1D 或者 2D 变换. 结果不被缩放. 当然 CV_DXT_FORWARD 和 CV_DXT_INVERSE 是互斥的. CV_DXT_SCALE - 对结果进行缩放 : 用数组元素除以它. 通常, 它和 CV_DXT_INVERSE 组合在一起, 可以使用缩写 CV_DXT_INV_SCALE. CV_DXT_ROWS - 输入矩阵的每个独立的行进行整型或者逆向变换 这个标志允许用户同时变换多个向量, 减少开销 ( 它往往比处理它自己要快好几倍 ), 进行 3D 和高维的变换等等 nonzero_rows 输入矩阵中非 0 行的个数 ( 在 2 维的 Forward 变换中 ), 或者是输出矩阵中感兴趣的行 ( 在反向的 2 维变换中 ) 如果这个值是负数,0, 或者大于总行数的一个值, 它将会被忽略 这个参数可以用来加速 2 维 DFT/IDFT 的速度 见下面的例子 函数 cvdft 执行一维或者二维浮点数组的离散傅立叶正变换或者离散傅立叶逆变换 : N 元一维向量的正向傅立叶变换 : y = F(N) x, 这里 F(N)jk=exp(-i 2Pi j k/n), i=sqrt(-1) 133

134 N 元一维向量的逆向傅立叶变换 : x'= (F(N))-1 y = conj(f(n)) y x = (1/N) x M N 元二维向量的正向傅立叶变换 : Y = F(M) X F(N) M N 元二维向量的逆向傅立叶变换 : X'= conj(f(m)) Y conj(f(n)) X = (1/(M N)) X' 假设时实数数据 ( 单通道 ), 从 IPL 借鉴过来的压缩格式被用来表现一个正向傅立叶变换的结果或者逆向傅立叶变换的输入 : Re Y0,0: Re Y0,1:Im Y0,1:Re Y0,2: Im Y0,2... Re Y0,N/2-1 Im Y0,N/2-1 Re Y0,N/2 Re Y1,0: Re Y1,1:Im Y1,1:Re Y1,2: Im Y1,2... Re Y1,N/2-1 Im Y1,N/2-1 Re Y1,N/2 Im Y1,0: Re Y2,1:Im Y2,1:Re Y2,2: Im Y2,2... Re Y2,N/2-1 Im Y2,N/2-1 Im Y2,N/ Re YM/2-1,0 Re YM-3,1 Im YM-3,1 Re YM-3,2 Im YM-3,2... Re YM-3,N/2-1 Im YM-3,N/2-1 Re YM-3,N/2 Im YM/2-1,0 Re YM-2,1 Im YM-2,1 Re YM-2,2 Im YM-2,2... Re YM-2,N/2-1 Im YM-2,N/2-1 Im YM-2,N/2 Re YM/2,0:Re YM-1,1 Im YM-1,1 Re YM-1,2 Im YM-1,2... Re YM-1,N/2-1 Im YM-1,N/2-1 Im YM-1,N/2 注意 : 如果 N 时偶数最后一列存在 (is present), 如果 M 时偶数最后一行 (is present). 如果是一维实数的变换结果就像上面矩阵的第一行的形式 利用 DFT 求解二维卷积 CvMat* A = cvcreatemat( M1, N1, CV_32F ); CvMat* B = cvcreatemat( M2, N2, A->type ); // it is also possible to have only abs(m2-m1)+1 abs(n2-n1)+1 // part of the full convolution result CvMat* conv = cvcreatemat( A->rows + B->rows - 1, A->cols + B->cols - 1, A->type ); // initialize A and B 134

135 ... int dft_m = cvgetoptimaldftsize( A->rows + B->rows - 1 ); int dft_n = cvgetoptimaldftsize( A->cols + B->cols - 1 ); CvMat* dft_a = cvcreatemat( dft_m, dft_n, A->type ); CvMat* dft_b = cvcreatemat( dft_m, dft_n, B->type ); CvMat tmp; // copy A to dft_a and pad dft_a with zeros cvgetsubrect( dft_a, &tmp, cvrect(0,0,a->cols,a->rows)); cvcopy( A, &tmp ); cvgetsubrect( dft_a, &tmp, cvrect(a->cols,0,dft_a->cols - A->cols,A->rows)); cvzero( &tmp ); // no need to pad bottom part of dft_a with zeros because of // use nonzero_rows parameter in cvdft() call below cvdft( dft_a, dft_a, CV_DXT_FORWARD, A->rows ); // repeat the same with the second array cvgetsubrect( dft_b, &tmp, cvrect(0,0,b->cols,b->rows)); cvcopy( B, &tmp ); cvgetsubrect( dft_b, &tmp, cvrect(b->cols,0,dft_b->cols - B->cols,B->rows)); cvzero( &tmp ); // no need to pad bottom part of dft_b with zeros because of // use nonzero_rows parameter in cvdft() call below cvdft( dft_b, dft_b, CV_DXT_FORWBRD, B->rows ); cvmulspectrums( dft_a, dft_b, dft_a, 0 /* or CV_DXT_MUL_CONJ to get correlation ::::::::::: rather than convolution */ ); part cvdft( dft_a, dft_a, CV_DXT_INV_SCALE, conv->rows ); // calculate only the top cvgetsubrect( dft_a, &tmp, cvrect(0,0,conv->cols,conv->rows) ); cvcopy( &tmp, conv ); GetOptimalDFTSize 对于给定的矢量尺寸返回最优 DFT 尺寸 135

136 int cvgetoptimaldftsize( int size0 ); size0 矢量长度. 函数 cvgetoptimaldftsize 返回最小值 N that is greater to equal to size0, such that DFT of a vector of size N can be computed fast. In the current implementation N=2p 3q 5r for some p, q, r. The function returns a negative number if size0 is too large (very close to INT_MAX) MulSpectrums 两个傅立叶频谱的每个元素的乘法 (Performs per-element multiplication of two Fourier spectrums) void cvmulspectrums( const CvArr* src1, const CvArr* src2, CvArr* dst, int flags ); src1 src2 dst flags 第一输入数组第二输入数组输出数组, 和输入数组有相同的类型和大小 下面列举的值的组合 : CV_DXT_ROWS - 把数组的每一行视为一个单独的频谱 ( 参见 cvdft 的参数讨论 ). CV_DXT_MUL_CONJ - 在做乘法之前取第二个输入数组的共轭. 函数 cvmulspectrums 执行两个 CCS-packed 或者实数或复数傅立叶变换的结果复数矩阵的每个元素的乘法 (performs per-element multiplication of the two CCS-packed or complex matrices that are results of real or complex Fourier transform.) 该函数和 cvdft 可以用来快速计算两个数组的卷积. DCT 执行一维或者二维浮点数组的离散馀弦变换或者离散反馀弦变换 #define CV_DXT_FORWARD 0 #define CV_DXT_INVERSE 1 #define CV_DXT_ROWS: 4 void cvdct( const CvArr* src, CvArr* dst, int flags ); src 136

137 dst flags 输入数组, 1D 或者 2D 实数数组. 输出数组, 和输入数组有相同的类型和大小 变换标志符, 下面值的组合 : CV_DXT_FORWARD - 1D 或者 2D 馀弦变换. CV_DXT_INVERSE - 1D or 2D 反馀弦变换. CV_DXT_ROWS - 对输入矩阵的每个独立的行进行馀弦或者反馀弦变换. 这个标志允许用户同时进行多个向量的变换, 可以用来减少开销 ( 它往往比处理它自己要快好几倍 ), 以及 3D 和高维变换等等 函数 cvdct 执行一维或者二维浮点数组的离散馀弦变换或者离散反馀弦变换 : N 元一维向量的馀弦变换 : y = C(N) x, 这里 C(N)jk=sqrt((j==0?1:2)/N) cos(pi (2k+1) j/n) N 元一维向量的反馀弦变换 : x = (C(N))-1 y = (C(N))T y M N 元二维向量的馀弦变换 : Y = (C(M)) X (C(N))T M N 元二维向量的反馀弦变换 : X = (C(M))T Y C(N) 3.Cxcore 动态结构 内存存储 (memory storage) CvMemStorage Growing memory storage typedef struct CvMemStorage struct CvMemBlock* bottom;/* first allocated block */ struct CvMemBlock* top; /* the current memory block - top of the stack */ struct CvMemStorage* parent; /* borrows new blocks from */ int block_size; /* block size */ int free_space; /* free space in the top block (in bytes) */ } CvMemStorage; 内存存储器是一个可用来存储诸如序列, 轮廓, 图形, 子划分等动态增长数据结构的底层结构 它是由一系列以同等大小的内存块构成, 呈列表型 ---bottom 域指的是列首,top 域指的是当前指向的块但未必是列尾. 在 bottom 和 top 之间所有的块 ( 包括 bottom, 不包括 137

138 top) 被完全占据了空间 ; 在 top 和列尾之间所有的块 ( 包括块尾, 不包括 top) 则是空的 ; 而 top 块本身则被占据了部分空间 -- free_space 指的是 top 块剩馀的空字节数 新分配的内存缓冲区 ( 或显式的通过 cvmemstoragealloc 函数分配, 或隐式的通过 cvseqpush, cvgraphaddedge 等高级函数分配 ) 总是起始于当前块 ( 即 top 块 ) 的剩馀那部分, 如果剩馀那部分能满足要求 ( 够分配的大小 ) 分配后,free_space 就减少了新分配的那部分内存大小, 外加一些用来保存适当列型的附加大小 当 top 块的剩馀空间无法满足被分配的块 ( 缓冲区 ) 大小时,top 块的下一个存储块被置为当前块 ( 新的 top 块 ) -- free_space 被置为先前分配的整个块的大小 如果已经不存在空的存储块 ( 即 :top 块已是列尾 ), 则必须再分配一个新的块 ( 或从 parent 那继承, 见 cvcreatechildmemstorage) 并将该块加到列尾上去 于是, 存储器 (memory storage) 就如同栈 (Stack) 那样, bottom 指向栈底,(top, free_space) 对指向栈顶 栈顶可通过 cvsavememstoragepos 保存, 通过 cvrestorememstoragepos 恢复指向, 通过 cvclearstorage 重置 [ ] CvMemBlock 内存存储块结构 typedef struct CvMemBlock struct CvMemBlock* prev; struct CvMemBlock* next; } CvMemBlock; CvMemBlock 代表一个单独的内存存储块结构 内存存储块中的实际数据存储在 header 块之后 ( 即 : 存在一个头指针 head 指向的块 header, 该块不存储数据 ), 于是, 内存块的第 i 个字节可以通过表达式 ((char*)(mem_block_ptr+1))[i] 获得 然而, 通常没必要直接去获得存储结构的域 [ ] CvMemStoragePos 内存存储块地址 typedef struct CvMemStoragePos CvMemBlock* top; int free_space; } CvMemStoragePos; 138

139 该结构 ( 如以下所说 ) 保存栈顶的地址, 栈顶可以通过 cvsavememstoragepos 保存, 也可以通过 cvrestorememstoragepos 恢复 [ ] CreateMemStorage 创建内存块 CvMemStorage* cvcreatememstorage( int block_size=0 ); block_size 存储块的大小以字节表示 如果大小是 0 byte, 则将该块设置成默认值 -- 当前默认大小为 64k. 函数 cvcreatememstorage 创建一内存块并返回指向块首的指针 起初, 存储块是空的 头部 ( 即 :header) 的所有域值都为 0, 除了 block_size 外. [ ] CreateChildMemStorage 创建子内存块 CvMemStorage* cvcreatechildmemstorage( CvMemStorage* parent ); parent 父内存块 函数 cvcreatechildmemstorage 创建一类似于普通内存块的子内存块, 除了内存分配 / 释放机制不同外 当一个子存储块需要一个新的块加入时, 它就试图从 parent 那得到这样一个块 如果 parent 中还未被占据空间的那些块中的第一个块是可获得的, 就获取第一个块 ( 依此类推 ), 再将该块从 parent 那里去除 如果不存在这样的块, 则 parent 要么分配一个, 要么从它自己 parent ( 即 :parent 的 parent) 那借个过来 换句话说, 完全有可能形成一个链或更为复杂的结构, 其中的内存存储块互为 child/ parent 关系 ( 父子关系 ) 当子存储结构被释放或清除, 它就把所有的块还给各自的 parent. 在其他方面, 子存储结构同普通存储结构一样 子存储结构在下列情况中是非常有用的 想象一下, 如果用户需要处理存储在某个块中的动态数据, 再将处理的结果存放在该块中 在使用了最简单的方法处理后, 临时数据作为输入和输出数据被存放在了同一个存储块中, 于是该存储块看上去就类似下面处理后的样子 : Dynamic data processing without using child storage. 结果, 在存储块中, 出现了垃圾 ( 临时数据 ) 然而, 如果在开始处理数据前就先建立一个子存储块, 将临时数据写入子存储块中并在最后释放子存储块, 那么最终在源 / 目的存储块 (source / destination storage) 中就不会出现垃圾, 于是该存储块看上去应该是如下形式 :Dynamic data processing using a child storage. [ ] 139

140 ReleaseMemStorage 释放内存块 void cvreleasememstorage( CvMemStorage** storage ); storage 指向被释放了的存储块的指针函数 cvreleasememstorage 释放所有的存储 ( 内存 ) 块或者将它们返回给各自的 parent ( 如果需要的话 ) 接下来再释放 header 块 ( 即 : 释放头指针 head 指向的块 = free(head)) 并清除指向该块的指针 ( 即 :head = NULL) 在释放作为 parent 的块之前, 先清除各自的 child 块 [ ] ClearMemStorage 清空内存存储块 void cvclearmemstorage( CvMemStorage* storage ); storage 存储存储块函数 cvclearmemstorage 将存储块的 top 置到存储块的头部 ( 注 : 清空存储块中的存储内容 ) 该函数并不释放内存( 仅清空内存 ) 假使该内存块有一个父内存块( 即 : 存在一内存块与其有父子关系 ), 则函数就将所有的块返回给其 parent. [ ] MemStorageAlloc 在存储块中分配以内存缓冲区 void* cvmemstoragealloc( CvMemStorage* storage, size_t size ); storage 内存块. size 缓冲区的大小. 函数 cvmemstoragealloc 在存储块中分配一内存缓冲区 该缓冲区的大小不能超过内存块的大小, 否则就会导致运行时错误 缓冲区的地址被调整为 CV_STRUCT_ALIGN 字节 ( 当前为 sizeof(double)). [ ] MemStorageAllocString 在存储块中分配一文本字符串 140

141 typedef struct CvString int len; char* ptr; } CvString; CvString cvmemstorageallocstring( CvMemStorage* storage, const char* ptr, int len=-1 ); storage 存储块 ptr 字符串 len 字符串的长度 ( 不计算 \0 ) 如果参数为负数, 函数就计算该字符串的长度 函数 cvmemstorageallostring 在存储块中创建了一字符串的拷贝 它返回一结构, 该结构包含字符串的长度 ( 该长度或通过用户传递, 或通过计算得到 ) 和指向被拷贝了的字符串的指针 [ ] SaveMemStoragePos 保存内存块的位置 ( 地址 ) void cvsavememstoragepos( const CvMemStorage* storage, CvMemStoragePos* pos ); storage 内存块. pos 内存块顶部位置 函数 cvsavememstoragepos 将存储块的当前位置保存到参数 pos 中 函数 cvrestorememstoragepos 可进一步获取该位置 ( 地址 ) [ ] RestoreMemStoragePos 恢复内存存储块的位置 void cvrestorememstoragepos( CvMemStorage* storage, CvMemStoragePos* pos ); storage 内存块. 141

142 pos 新的存储块的位置 函数 cvrestorememstoragepos 通过参数 pos 恢复内存块的位置 该函数和函数 cvclearmemstorage 是释放被占用内存块的唯一方法 注意 : 没有什么方法可去释放存储块中被占用的部分内存 [ ] 序列 [ ] CvSeq 可动态增长元素序列 (OpenCV_1.0 已发生改变, 详见 cxtypes.h) Growable sequence of elements #define CV_SEQUENCE_FIELDS() \ int flags; /* micsellaneous flags */ \ int header_size; /* size of sequence header */ \ struct CvSeq* h_prev; /* previous sequence */ \ struct CvSeq* h_next; /* next sequence */ \ struct CvSeq* v_prev; /* 2nd previous sequence */ \ struct CvSeq* v_next; /* 2nd next sequence */ \ int total; /* total number of elements */ \ int elem_size;/* size of sequence element in bytes */ \ char* block_max;/* maximal bound of the last block */ \ char* ptr; /* current write pointer */ \ int delta_elems; /* how many elements allocated when the sequence grows (sequence granularity) */ \ CvMemStorage* storage; /* where the seq is stored */ \ CvSeqBlock* free_blocks; /* free blocks list */ \ CvSeqBlock* first; /* pointer to the first sequence block */ typedef struct CvSeq CV_SEQUENCE_FIELDS() } CvSeq; 结构 CvSeq 是所有 OpenCV 动态数据结构的基础 在 1.0 版本中, 将前六个成员剥离出来定义成一个宏. 通过不同寻常的宏定义简化了带有附加参数的结构 CvSeq 的扩展 为了扩展 CvSeq, 用户可以定义一新的数据结构或在通过宏 CV_SEQUENCE_FIELDS() 所包括的 CvSeq 的域后在放入用户自定义的域 142

143 有两种类型的序列 -- 稠密序列和稀疏序列 稠密序列都派生自 CvSeq, 它们用来代表可扩展的一维数组 -- 向量, 栈, 队列, 双端队列 数据间不存在空隙 ( 即 : 连续存放 )-- 如果元素从序列中间被删除或插入新的元素到序列中 ( 不是两端 ), 那么此元素后边的相关元素会被移动 稀疏序列都派生自 CvSet, 后面会有详细的讨论 它们都是由节点所组成的序列, 每一个节点要么被占用空间要么是空, 由 flag 标志指定 这些序列作为无序的数据结构而被使用, 如点集, 图, 哈希表等 域 header_size( 结构的大小 ) 含有序列头部节点的实际大小, 此大小大于或等于 sizeof(cvseq). 当这个宏用在序列中时, 应该等于 sizeof(cvseq), 若这个宏用在其他结构中, 如 CvContour, 结构的大小应该大于 sizeof(cvseq); 域 h_prev, h_next, v_prev, v_next 可用来创建不同序列的层次结构 域 h_prev, h_next 指向同一层次结构前一个和后一个序列, 而域 v_prev, v_next 指向在垂直方向上的前一个和后一个序列, 即 : 父亲和子孙 域 first 指向第一个序列快, 块结构在后面描述 域 total 包含稠密序列的总元素数和稀疏序列被分配的节点数 域 flags 的高 16 位描述 ( 包含 ) 特定的动态结构类型 (CV_SEQ_MAGIC_VAL 表示稠密序列,CV_SET_MAGIC_VAL 表示稀疏序列 ), 同时包含形形色色的信息 低 CV_SEQ_ELTYPE_BITS 位包含元素类型的 ID( 标示符 ) 大多数处理函数并不会用到元素类型, 而会用到存放在 elem_size 中的元素大小 如果序列中包含 CvMat 中的数据, 那么元素的类型就与 CvMat 中的类型相匹配, 如 :CV_32SC2 可以被使用为由二维空间中的点序列, CV_32FC1 用为由浮点数组成的序列等 通过宏 CV_SEQ_ELTYPE(seq_header_ptr) 来获取序列中元素的类型 处理数字序列的函数判断 : elem.size 等同于序列元素的大小 除了与 CvMat 相兼容的类型外, 还有几个在头 cvtypes.h 中定义的额外的类型 Standard Types of Sequence Elements #define CV_SEQ_ELTYPE_POINT CV_32SC2 /* (x,y) */ #define CV_SEQ_ELTYPE_CODE CV_8UC1 /* freeman code: 0..7 */ #define CV_SEQ_ELTYPE_GENERIC 0 /* unspecified type of sequence elements */ #define CV_SEQ_ELTYPE_PTR CV_USRTYPE1 /* =6 */ #define CV_SEQ_ELTYPE_PPOINT CV_SEQ_ELTYPE_PTR /* &elem: pointer to element of other sequence */ #define CV_SEQ_ELTYPE_INDEX CV_32SC1 /* #elem: index of element of some other sequence */ #define CV_SEQ_ELTYPE_GRAPH_EDGE CV_SEQ_ELTYPE_GENERIC /* &next_o, &next_d, &vtx_o, &vtx_d */ #define CV_SEQ_ELTYPE_GRAPH_VERTEX CV_SEQ_ELTYPE_GENERIC /* first_edge, &(x,y) */ 143

144 #define CV_SEQ_ELTYPE_TRIAN_ATR CV_SEQ_ELTYPE_GENERIC /* vertex of the binary tree */ #define CV_SEQ_ELTYPE_CONNECTED_COMP CV_SEQ_ELTYPE_GENERIC /* connected component */ #define CV_SEQ_ELTYPE_POINT3D CV_32FC3 /* (x,y,z) */ 后面的 CV_SEQ_KIND_BITS 字节表示序列的类型 : Standard Kinds of Sequences /* generic (unspecified) kind of sequence */ #define CV_SEQ_KIND_GENERIC (0 << CV_SEQ_ELTYPE_BITS) /* dense sequence suntypes */ #define CV_SEQ_KIND_CURVE #define CV_SEQ_KIND_BIN_TREE (1 << CV_SEQ_ELTYPE_BITS) (2 << CV_SEQ_ELTYPE_BITS) /* sparse sequence (or set) subtypes */ #define CV_SEQ_KIND_GRAPH (3 << CV_SEQ_ELTYPE_BITS) #define CV_SEQ_KIND_SUBDIV2D (4 << CV_SEQ_ELTYPE_BITS) [ ] CvSeqBlock 连续序列块 typedef struct CvSeqBlock struct CvSeqBlock* prev; /* previous sequence block */ struct CvSeqBlock* next; /* next sequence block */ int start_index; /* index of the first element in the block + sequence->first->start_index */ int count; /* number of elements in the block */ char* data; /* pointer to the first element of the block */ } CvSeqBlock; 序列块构成一个双向的循环列表, 因此指针 prev 和 next 永远不会为 null, 而总是指向序列中的前一个和后一个序列块 也就是说 : 最后一个序列块的 next 指向的就是序列中的第一个块, 而第一个块的 prev 指向最后一个块 域 start_index 和 count 有助于跟踪序列中块的位置 例如, 一个含 10 个元素的序列被分成了 3 块, 每一块的大小分别为 3, 5, 144

145 2, 第一块的参数 start_index 为 2, 那么该序列的 (start_index, count) 相应为 (2, 3),(5,5),(10,2) 第一个块的参数 start_index 通常为 0, 除非一些元素已被插入到序列中 [ ] CvSlice 序列分割 typedef struct CvSlice int start_index; int end_index; } CvSlice; inline CvSlice cvslice( int start, int end ); #define CV_WHOLE_SEQ_END_INDEX 0x3fffffff #define CV_WHOLE_SEQ cvslice(0, CV_WHOLE_SEQ_END_INDEX) /* calculates the sequence slice length */ int cvslicelength( CvSlice slice, const CvSeq* seq ); 有关序列的一些操作函数将 CvSlice 作为输入参数, 默认情况下该参数通常被设置成整个序列 (CV_WHOLE_SEQ) start_index 和 end_index 任何一个都可以是负数或超过序列长度,start_index 是闭界,end_index 是开界 如果两者相等, 那么分割被认为是空分割 ( 即 : 不包含任何元素 ) 由于序列被看作是循环结构, 所以分割可以选择序列中靠后的几个元素, 靠前的参数反而跟着它们, 如 cvslice(-2,3) 函数用下列方法来规范分割参数: 首先, 调用 cvslicelength 来决定分割的长度, 然后, start_index 被使用类似于 cvgetseqelem 的参数来规范 ( 例如 : 负数也被允许 ) 实际的分割操作起始于规范化了的 start_index, 中止于 start_index + cvslicelength() ( 再次假设序列是循环结构 ) 如果函数并不接受分割参数, 但你还是想要处理序列的一部分, 那么可以使用函数 cvseqslice 获取子序列 [ ] CreateSeq 创建一序列 CvSeq* cvcreateseq( int seq_flags, int header_size, int elem_size, CvMemStorage* storage ); seq_flags 145

146 序列的符号标志 如果序列不会被传递给任何使用特定序列的函数, 那么将它设为 0, 否则从预定义的序列类型中选择一合适的类型 header_size 序列头部的大小 ; 必须大于或等于 sizeof(cvseq). 如果制定了类型或它的扩展名, 则此类型必须适合基类的头部大小 elem_size 元素的大小, 以字节计 这个大小必须与序列类型相一致 例如, 对于一个点的序列, 元素类型 CV_SEQ_ELTYPE_POINT 应当被指定, 参数 elem_size 必须等同于 sizeof(cvpoint). 函数 cvcreateseq 创建一序列并且返回指向该序列的指针 函数在存储块中分配序列的头部作为一个连续躯体, 并且设置结构的 flags 域, elem_size 域, header_size 域和 storage 域的值为被传递过来的值, 设置 delta_elems 为默认值 ( 可通过函数 cvsetseqblocksize 重新对其赋值 ), 清空其他的头部域, 包括前 sizeof(cvseq) 个字节的空间 [ ] SetSeqBlockSize 设置序列块的大小 void cvsetseqblocksize( CvSeq* seq, int delta_elems ); seq 序列 delta_elems 满足元素所需的块的大小函数 cvsetseqblocksize 会对内存分配的粒度产生影响 当序列缓冲区中空间消耗完时, 函数为 delta_elems 个序列元素分配空间 如果新分配的空间与之前分配的空间相邻的话, 这两个块就合并, 否则, 就创建了一个新的序列快 因此, 参数值越大, 序列中出现碎片的可能性就越小, 不过内存中更多的空间将被浪费 当序列被创建后, 参数 delta_elems 大小将被设置为默认大小 (1K). 之后, 就可随时调用该函数, 并影响内存分配 函数可以修改被传递过来的参数值, 以满足内存块的大小限制 [ ] SeqPush 添加元素到序列的尾部 char* cvseqpush( CvSeq* seq, void* element=null ); seq 块 element 添加的元素 146

147 函数 cvseqpush 在序列块的尾部添加一元素并返回指向该元素得指针 如果输入参数为 null, 函数就仅仅分配一空间, 留给下一个元素使用 下列代码说明如何使用该函数去创建一空间 The following code demonstrates how to create a new sequence using this function: CvMemStorage* storage = cvcreatememstorage(0); CvSeq* seq = cvcreateseq( CV_32SC1, /* sequence of integer elements */ sizeof(cvseq), /* header size - no extra fields */ sizeof(int), /* element size */ storage /* the container storage */ ); int i; for( i = 0; i < 100; i++ ) int* added = (int*)cvseqpush( seq, &i ); printf( "%d is added\n", *added ); }... /* release memory storage in the end */ cvreleasememstorage( &storage ); 函数 cvseqpush 的时间复杂度为 O(1). 如果需要分配并使用的空间比较大, 则存在一分配较快的函数 ( 见 :cvstartwriteseq 和相关函数 ) [ ] SeqPop 删除序列尾部元素 void cvseqpop( CvSeq* seq, void* element=null ); seq 序列 element 可选参数 如果该指针不为空, 就拷贝被删元素到指针指向的位置函数 cvseqpop 从序列中删除一元素 如果序列已经为空, 就报告一错误 函数时间复杂度为 O(1). [ ] SeqPushFront 在序列头部添加元素 147

148 char* cvseqpushfront( CvSeq* seq, void* element=null ); seq 序列 element 添加的元素函数 cvseqpushfront 类似于 cvseqpush, 不过是在序列头部添加元素 时间复杂度为 O(1). [ ] SeqPopFront 删除序列的头部元素 void cvseqpopfront( CvSeq* seq, void* element=null ); seq 序列 element 可选参数 如果该指针不为空, 就拷贝被珊元素到指针指向的位置 函数 cvseqpopfront 删除序列的头部元素 如果序列已经为空, 就报告一错误 函数时间复杂度为 O(1). [ ] SeqPushMulti 添加多个元素到序列尾部或头部 void cvseqpushmulti( CvSeq* seq, void* elements, int count, int in_front=0 ); seq 序列 elements 待添加的元素 count 添加的元素个数 in_front 标示在头部还是尾部添加元素 CV_BACK ( = 0) -- 在序列尾部添加元素 CV_FRONT(!= 0) -- 在序列头部添加元素 函数 cvseqpushmulti 在序列头部或尾部添加多个元素 元素按输入数组中的顺序被添加到序列中, 不过它们可以添加到不同的序列中 [ ] SeqPopMulti 148

149 删除多个序列头部或尾部的元素 void cvseqpopmulti( CvSeq* seq, void* elements, int count, int in_front=0 ); seq 序列 elements 待删除的元素 count 删除的元素个数 in_front 标示在头部还是尾部删除元素 CV_BACK ( = 0) -- 删除序列尾部元素 CV_FRONT(!= 0) -- 删除序列头部元素 函数 cvseqpopmulti 删除多个序列头部或尾部的元素 如果待删除的元素个数超过了序列中的元素总数, 则函数删除尽可能多的元素 [ ] SeqInsert 在序列中添加元素 char* cvseqinsert( CvSeq* seq, int before_index, void* element=null ); seq 序列 before_index 元素插入的位置 ( 索引 ) 如果插入的位置在 0( 允许的参数最小值 ) 前, 则该函数等同于函数 cvseqpushfront. 如果是在 seq_total( 允许的参数最大值 ) 后, 则该函数等同于 cvseqpush. element 待插入的元素函数 cvseqinsert 移动从被插入的位置到序列尾部元素所在的位置的所有元素, 如果指针 element 不位 null, 则拷贝 element 中的元素到指定位置 函数返回指向被插入元素的指针 [ ] SeqRemove 从序列中删除指定的元素 void cvseqremove( CvSeq* seq, int index ); seq 目标序列 149

150 index 被删除元素的索引或位置 函数 cvseqremove 删除 seq 中指定索引 ( 位置 ) 的元素 如果这个索引超出序列的元素个数, 会报告出错 企图从空序列中删除元素, 函数也将报告错误 函数通过移动序列中的元素来删除被索引的元素 [ ] ClearSeq 清空序列 void cvclearseq( CvSeq* seq ); seq seq Sequence. 序列 函数 cvclearseq 删除序列中的所有元素 函数不会将内存返回到存储器中, 当新的元素添加到序列中时, 可重新使用该内存 函数时间复杂度为 O(1). [ ] GetSeqElem 返回索引所指定的元素指针 char* cvgetseqelem( const CvSeq* seq, int index ); #define CV_GET_SEQ_ELEM( TYPE, seq, index ) (TYPE*)cvGetSeqElem( (CvSeq*)(seq), (index) ) seq index 序列 索引 函数 cvgetseqelem 查找序列中索引所指定的元素, 并返回指向该元素的指针 如果元素不存在, 则返回 0 函数支持负数, 即 : -1 代表序列的最后一个元素, -2 代表最后第二个元素, 等 如果序列只包含一个块, 或者所需的元素在第一个块中, 那么应当使用宏, CV_GET_SEQ_ELEM( elemtype, seq, index ) 宏中的参数 elemtype 是序列中元素的类型 ( 如 : CvPoint), 参数 seq 表示序列, 参数 index 代表所需元素的索引 该宏首先核查所需的元素是否属于第一个块, 如果是, 则返回该元素, 否则, 该宏就调用主函数 GetSeqElem. 如果索引为负数的话, 则总是调用函数 cvgetseqelem 函数的时间复杂度为 O(1), 假设块的大小要比元素的数量要小 [ ] SeqElemIdx 150

151 返回序列中元素的索引 int cvseqelemidx( const CvSeq* seq, const void* element, CvSeqBlock** block=null ); seq 序列 element 指向序列中元素的指针 block 可选参数, 如果不为空 (NULL), 则存放包含该元素的块的地址函数 cvseqelemidx 返回元素的索引, 如果该元素不存在这个序列中, 则返回一负数 [ ] cvseqtoarray 拷贝序列中的元素到一个连续的内存块中 void* cvcvtseqtoarray( const CvSeq* seq, void* elements, CvSlice slice=cv_whole_seq ); seq 序列 elemenets 指向目的 ( 存放拷贝元素的 ) 数组的指针, 指针指向的空间必须足够大 slice 拷贝到序列中的序列部分 函数 cvcvtseqtoarray 拷贝整个序列或部分序列到指定的缓冲区中, 并返回指向该缓冲区的指针. [ ] MakeSeqHeaderForArray 构建序列 CvSeq* cvmakeseqheaderforarray( int seq_type, int header_size, int elem_size, void* elements, int total, CvSeq* seq, CvSeqBlock* block ); seq_type 序列的类型 header_size 序列的头部大小 大小必须大于等于数组的大小 elem_size 元素的大小 151

152 elements 形成该序列的元素 total 序列中元素的总数 参数值必须等于数据的大小 seq 指向被使用作为序列头部的局部变量 block 指向局部变量的指针函数 cvmakeseqheaderforarray 初始化序列的头部 序列块由用户分配 ( 例如 : 在栈上 ) 该函数不拷贝数据 创建的序列只包含一个块, 和一个 NULL 指针, 因此可以读取指针, 但试图将元素添加到序列中则多数会引发错误 [ ] SeqSlice 为各个序列碎片建立头 CvSeq* cvseqslice( const CvSeq* seq, CvSlice slice, CvMemStorage* storage=null, int copy_data=0 ); seq 序列 slice 部分序列块 storage 存放新的序列和拷贝数据 ( 如果需要 ) 的目的存储空间 如果为 NULL, 则函数使用包含该输入数据的存储空间 copy_data 标示是否要拷贝元素, 如果 copy_data!= 0, 则需要拷贝 ; 如果 copy_data == 0, 则不需拷贝 函数 cvseqslice 创建一序列, 该序列表示输入序列中特定的一部分 (slice), 新序列要么与原序列共享元素要么拥有自己的一份拷贝 因此, 如果有人需要去处理该部分序列, 但函数却没有 slice 参数, 则使用该函数去获取该序列. [ ] CloneSeq 创建序列的一份拷贝 CvSeq* cvcloneseq( const CvSeq* seq, CvMemStorage* storage=null ); seq 序列 storage 152

153 存放新序列的 header 部分和拷贝数据 ( 如果需要 ) 的目的存储块 如果为 NULL, 则函数使用包含输入序列的存储块 函数 cvcloneseq 创建输入序列的一份完全拷贝 调用函数 cvcloneseq (seq, storage) 等同于调用 cvseqslice(seq, CV_WHOLE_SEQ, storage, 1). [ ] SeqRemoveSlice 删除序列的 slice 部分 void cvseqremoveslice( CvSeq* seq, CvSlice slice ); seq slice 序列 序列中被移动的那部分 函数 cvseqremoveslice 删除序列中的 slice 部分 [ ] SeqInsertSlice 在序列中插入一数组 void cvseqinsertslice( CvSeq* seq, int before_index, const CvArr* from_arr ); seq 序列 slice 序列中被移动的那部分 from_arr 从中获取元素的数组函数 cvseqinsertslice 在指定位置插入来自数组 from_arr 中所有元素 数组 from_arr 可以是一个矩阵也可以是另外一个序列 [ ] SeqInvert 将序列中的元素进行逆序操作 void cvseqinvert( CvSeq* seq ); seq 序列 函数 cvseqinvert 对序列进行逆序操作 -- 即 : 使第一个元素成为最后一个, 最后一个元素为第一个 153

154 [ ] SeqSort 使用特定的比较函数对序列中的元素进行排序 /* a < b? -1 : a > b? 1 : 0 */ typedef int (CV_CDECL* CvCmpFunc)(const void* a, const void* b, void* userdata); void cvseqsort( CvSeq* seq, CvCmpFunc func, void* userdata=null ); seq 待排序的序列 func 比较函数, 按照元素间的大小关系返回负数, 零, 正数 ( 见 : 上面的声明和下面的例子 ) -- 相关函数为 C 运行时库中的 qsort, 后者 (qsort) 不使用参数 userdata. userdata 传递给比较函数的用户参数 ; 有些情况下, 可避免全局变量的使用函数 cvseqsort 使用特定的标准对序列进行排序 下面是一个使用该函数的实例 /* Sort 2d points in top-to-bottom left-to-right order */ static int cmp_func( const void* _a, const void* _b, void* userdata ) CvPoint* a = (CvPoint*)_a; CvPoint* b = (CvPoint*)_b; int y_diff = a->y - b->y; int x_diff = a->x - b->x; return y_diff? y_diff : x_diff; }... CvMemStorage* storage = cvcreatememstorage(0); CvSeq* seq = cvcreateseq( CV_32SC2, sizeof(cvseq), sizeof(cvpoint), storage ); int i; for( i = 0; i < 10; i++ ) CvPoint pt; pt.x = rand() % 1000; pt.y = rand() % 1000; cvseqpush( seq, &pt ); } 154

155 cvseqsort( seq, cmp_func, 0 /* userdata is not used here */ ); /* print out the sorted sequence */ for( i = 0; i < seq->total; i++ ) CvPoint* pt = (CvPoint*)cvSeqElem( seq, i ); printf( "(%d,%d)\n", pt->x, pt->y ); } cvreleasememstorage( &storage ); [ ] SeqSearch 查询序列中的元素 /* a < b? -1 : a > b? 1 : 0 */ typedef int (CV_CDECL* CvCmpFunc)(const void* a, const void* b, void* userdata); char* cvseqsearch( CvSeq* seq, const void* elem, CvCmpFunc func, int is_sorted, int* elem_idx, void* userdata=null ); seq 序列 elem 待查询的元素 func 比较函数, 按照元素间的大小关系返回负数, 零, 正数 ( 见 :cvseqsort) is_sorted 标示序列是否已经排序 elem_idx 输出参数 ;( 已查找到 ) 元素的索引值 user_data 传递到比较函数的用户参数 ; 在某些情况下, 有助于避免使用全局变量 函数 cvseqsearch 查找序列中的元素 如果序列已被排序, 则使用二分查找 ( 时间复杂度为 O(log(N)) 否则使用简单线性查找 若查找的元素不存在, 函数返回 NULL 指针, 而索引值设置为序列中的元素数 ( 如果使用的是线性查找 ) 或满足表达式 seq(i) > elem 的最小的 i. [ ] StartAppendToSeq 155

156 将数据写入序列中, 并初始化该过程 void cvstartappendtoseq( CvSeq* seq, CvSeqWriter* writer ); seq writer 指向序列的指针 writer 的状态 ; 由该函数初始化 函数 cvstartappendtoseq 初始化将数据写入序列这个过程 通过宏 CV_WRITE_SEQ_ELEM( written_elem, writer ), 写入的元素被添加到序列尾部 注意 : 在写入期间, 序列的其他操作可能会产生的错误的结果, 甚至破怀该序列 ( 见 cvflushseqwriter 的相关描述, 有助于避免这些错误 ) [ ] StartWriteSeq 创建新序列, 并初始化写入部分 (writer) void cvstartwriteseq( int seq_flags, int header_size, int elem_size, CvMemStorage* storage, CvSeqWriter* writer ); seq_flags 标示被创建的序列 如果序列还未传递给任何处理特定序列类型的函数, 则序列值等于 0, 否则, 必须从之前定义的序列类型中选择一个合适的类型 header_size 头部的大小 参数值不小于 sizeof(cvseq). 如果定义了某一类型, 则该类型不许符合基类的条件 elem_size 元素的大小 ( 以字节计 ); 必须与序列类型相一致 例如 : 如果创建了包含指针的序列 ( 元素类型为 CV_SEQ_ELTYPE_POINT), 那么 elem_size 必须等同于 sizeof(cvpoint). storage 序列的 ( 在内存 ) 位置 writer 写入部分 writer 的状态 ; 由该函数初始化函数 cvstartwriteseq 是函数 cvcreateseq 和函数 cvstartappendtoseq 的组合 指向被创建的序列的指针存放在 writer->seq 中, 通过函数 cvendwriteseq 返回 ( 因当在最后调用 ) [ ] EndWriteSeq 完成写入操作 156

157 CvSeq* cvendwriteseq( CvSeqWriter* writer ); writer 写入部分 writer 的状态 函数 cvendwriteseq 完成写入操作并返回指向被写入元素的序列的地址 同时, 函数会截取最后那个不完整的序列块, 将块的剩馀部分返回到内存中之后, 序列就可以被安全的读和写 [ ] FlushSeqWriter 根据写入状态, 刷新序列头部 void cvflushseqwriter( CvSeqWriter* writer ); writer 写入部分的状态 函数 cvflushseqwriter 用来使用户在写入过程中每当需要时读取序列元素, 比如说, 核查制定的条件 函数更新序列的头部, 从而使读取序列中的数据成为可能 不过, 写入并没有被关闭, 为的是随时都可以将数据写入序列 在有些算法中, 经常需要刷新, 考虑使用 cvseqpush 代替该函数 [ ] StartReadSeq 初始化序列中的读取过程 void cvstartreadseq( const CvSeq* seq, CvSeqReader* reader, int reverse=0 ); seq 序列 reader 读取部分的状态 ; 由该函数初始化 reverse 决定遍历序列的方向 如果 reverse 为 0, 则读取顺序被定位从序列头部元素开始, 否则从尾部开始读取函数 cvstartreadseq 初始化读取部分的状态 毕竟, 顺序读取可通过调用宏 CV_READ_SEQ_ELEM( read_elem, reader ), 逆序读取可通过调用宏 CV_REV_READ_SEQ_ELEM( read_elem, reader ) 这两个宏都将序列元素读进 read_elem 中, 并将指针移到下一个元素 下面代码显示了如何去使用 reader 和 writer. CvMemStorage* storage = cvcreatememstorage(0); 157

158 CvSeq* seq = cvcreateseq( CV_32SC1, sizeof(cvseq), sizeof(int), storage ); CvSeqWriter writer; CvSeqReader reader; int i; cvstartappendtoseq( seq, &writer ); for( i = 0; i < 10; i++ ) int val = rand()%100; CV_WRITE_SEQ_ELEM( val, writer ); printf("%d is written\n", val ); } cvendwriteseq( &writer ); cvstartreadseq( seq, &reader, 0 ); for( i = 0; i < seq->total; i++ ) int val; #if 1 CV_READ_SEQ_ELEM( val, reader ); printf("%d is read\n", val ); #else /* alternative way, that is prefferable if sequence elements are large, or their size/type is unknown at compile time */ printf("%d is read\n", *(int*)reader.ptr ); CV_NEXT_SEQ_ELEM( seq->elem_size, reader ); #endif }... cvreleasestorage( &storage ); [ ] GetSeqReaderPos 返回当前的读取器的位置 int cvgetseqreaderpos( CvSeqReader* reader ); reader 读取器的状态. 函数 cvgetseqreaderpos 返回当前的 reader 位置 ( 在 0 到 reader->seq->total - 1 中 ) [ ] 158

159 SetSeqReaderPos 移动读取器到指定的位置 void cvsetseqreaderpos( CvSeqReader* reader, int index, int is_relative=0 ); reader reader 的状态 index 目的位置 如果使用了 positioning mode, 则实际位置为 index % reader->seq->total. is_relative 如果不位 0, 那么索引 (index) 就相对于当前的位置 函数 cvsetseqreaderpos 将 read 的位置移动到绝对位置, 或相对于当前的位置 ( 相对位置 ) [ ] 集合 [ ] CvSet Collection of nodes typedef struct CvSetElem int flags; /* it is negative if the node is free and zero or positive otherwise */ struct CvSetElem* next_free; /* if the node is free, the field is a pointer to next free node */ } CvSetElem; #define CV_SET_FIELDS() \ CV_SEQUENCE_FIELDS() /* inherits from CvSeq */ \ struct CvSetElem* free_elems; /* list of free nodes */ typedef struct CvSet CV_SET_FIELDS() } CvSet; 在 OpenCV 的稀疏数据结构中, CvSet 是一基本结构 159

160 从上面的声明中可知 :CvSet 继承自 CvSeq, 并在此基础上增加了个 free_elems 域, 该域是空节点组成的列表 集合中的每一个节点, 无论空否, 都是线性表中的一个元素 尽管对于稠密的表中的元素没有限制, 集合 ( 派生的结构 ) 元素必须起始于整数域, 并与结构 CvSetElem 相吻合, 因为这两个域对于 ( 由空节点组成 ) 集合的组织是必要的 如果节点为空,flags 为负,next_free 指向下一个空节点 如果节点已被占据空间,flags 为正,flags 包含节点索引值 ( 使用表达式 set_elem->flags & CV_SET_ELEM_IDX_MASKH 获取 ), flags 的剩馀内容由用户决定 宏 CV_IS_SET_ELEM(set_elem.ptr) 用来识别特定的节点是否为空 起初, 集合 set 同表 list 都为空 当需要一个来自集合中的新节点时, 就从表 list 中去获取, 然后表进行了更新 如果表 list 碰巧为空, 于是就分配一内存块, 块中的所有节点与表 list 相连 结果, 集合的 total 域被设置为空节点和非空节点的和 当非空节点别释放后, 就将它加到空节点列表中 最先被释放的节点也就是最先被占用空间的节点在 OpenCV 中, CvSet 用来代表图形 (CvGraph), 稀疏多维数组 (CvSparseMat), 平面子划分 (planner subdivisions) 等 [ ] CreateSet 创建空的数据集 CvSet* cvcreateset( int set_flags, int header_size, int elem_size, CvMemStorage* storage ); set_flags 集合的类型 header_size 头节点的大小 ; 应该等于 sizeof(cvset) elem_size 元素的大小 ; 不能小 8 storage 相关容器函数 CvCreateSet 创建一具有特定头部节点大小和元素类型的空集 并返回指向该集合的指针 [ ] SetAdd 占用集合中的一个节点 int cvsetadd( CvSet* set_header, CvSetElem* elem=null, CvSetElem** inserted_elem=null ); set_header 160

161 elem 集合 可选的输入参数, 被插入的元素 如果不为 NULL, 函数就将数据拷贝到新分配的节点 ( 拷贝后, 清空第一个域的 MSB) 函数 cvsetadd 分配一新的节点, 将输入数据拷贝给它 ( 可选 ), 并且返回指向该节点的指针和节点的索引值 索引值可通过节点的 flags 域的低位中获得 函数的时间复杂度为 O(1), 不过, 存在着一个函数可快速的分配内存 ( 见 cvsetnew) [ ] SetRemove 从点集中删除元素 void cvsetremove( CvSet* set_header, int index ); set_header 集合 index 被删元素的索引值函数 cvsetremove 从点集中删除一具有特定索引值的元素 如果指定位置的节点为空, 函数将什么都不做 函数的时间复杂度为 O(1), 不过, 存在一函数可更快速的完成该操作, 该函数就是 cvsetremovebyptr [ ] SetNew 添加元素到点集中 CvSetElem* cvsetnew( CvSet* set_header ); set_header 集合函数 cvsetnew 是 cvsetadd 的变体, 内联函数 它占用一新节点, 并返回指向该节点的指针而不是索引 [ ] SetRemoveByPtr 删除指针指向的集合元素 void cvsetremovebyptr( CvSet* set_header, void* elem ); set_header 集合 elem 161

162 被删除的元素函数 cvsetremovebyptr 是一内联函数, 是函数 cvsetremove 轻微变化而来的 该函数并不会检查节点是否为空 -- 用户负责这一检查 [ ] GetSetElem 通过索引值查找相应的集合元素 CvSetElem* cvgetsetelem( const CvSet* set_header, int index ); set_header 集合 index 索引值函数 cvgetsetelem 通过索引值查找相应的元素 函数返回指向该元素的指针, 如果索引值无效或相应的节点为空, 则返回 0 若函数使用 cvgetseqelem 去查找节点, 则函数支持负的索引值 [ ] ClearSet 清空点集 void cvclearset( CvSet* set_header ); set_header 待清空的点集 函数 cvclearset 删除集合中的所有元素 时间复杂度为 O(1). [ ] 图 [ ] CvGraph 有向权图和无向权图 #define CV_GRAPH_VERTEX_FIELDS() \ int flags; /* vertex flags */ \ struct CvGraphEdge* first; /* the first incident edge */ typedef struct CvGraphVtx 162

163 CV_GRAPH_VERTEX_FIELDS() } CvGraphVtx; #define CV_GRAPH_EDGE_FIELDS() \ int flags; /* edge flags */ \ float weight; /* edge weight */ \ struct CvGraphEdge* next[2]; /* the next edges in the incidence lists for staring (0) */ \ /* and ending (1) vertices */ \ struct CvGraphVtx* vtx[2]; /* the starting (0) and ending (1) vertices */ typedef struct CvGraphEdge CV_GRAPH_EDGE_FIELDS() } CvGraphEdge; #define CV_GRAPH_FIELDS() \ CV_SET_FIELDS() /* set of vertices */ \ CvSet* edges; /* set of edges */ typedef struct CvGraph CV_GRAPH_FIELDS() } CvGraph; 在 OpenCV 图形结构中,CvGraph 是一基本结构 图形结构继承自 CvSet -- 该部分描绘了普通图的属性和图的顶点, 也包含了一个点集作为其成员 -- 该点集描述了图的边缘 利用宏 ( 可以简化结构扩展和定制 ) 使用与其它 OpenCV 可扩展结构一样的方法和技巧, 同样的方法和技巧, 我们声明了定点, 边和头部结构 虽然顶点结构和边结构无法从 CvSetElem 显式地继承时, 但它们满足点集元素的两个条件 ( 在开始是有一个整数域和满足 CvSetElem 结构 ) flags 域用来标记顶点和边是否已被占用或者处于其他目的, 如 : 遍历图时 ( 见 :cvstartscangraph 等 ), 因此最好不要去直接使用它们 图代表的就是边的集合 存在有向和无向的区别 对于后者 ( 无向图 ), 在连接顶点 A 到顶点 B 的边同连接顶点 B 到顶点 A 的边是没什么区别的, 在某一时刻, 只可能存在一个, 即 : 要么是 <A, B> 要么是 <B, A>. [ ] CreateGraph 163

164 创建一个空树 CvGraph* cvcreategraph( int graph_flags, int header_size, int vtx_size, int edge_size, CvMemStorage* storage ); graph_flags 被创建的图的类型 通常, 无向图为 CV_SEQ_KIND_GRAPH, 有向图为 CV_SEQ_KIND_GRAPH CV_GRAPH_FLAG_ORIENTED. header_size 头部大小 ; 可能小于 sizeof(cvgraph) vtx_size 顶点大小 ; 常规的定点结构必须来自 CvGraphVtx ( 使用宏 CV_GRAPH_VERTEX_FIELDS()) edge_size 边的大小 ; 常规的边结构必须来自 CvGraphEdge ( 使用宏 CV_GRAPH_EDGE_FIELDS()) storage 图的容器函数 cvcreategraph 创建一空图并且返回指向该图的指针 [ ] GraphAddVtx 插入一顶点到图中 int cvgraphaddvtx( CvGraph* graph, const CvGraphVtx* vtx=null, CvGraphVtx** inserted_vtx=null ); graph 图 vtx 可选输入参数, 用来初始化新加入的顶点 ( 仅大小超过 sizeof(cvgraphvtx) 的用户自定义的域才会被拷贝 ) inserted_vertex 可选的输出参数 如果不为 NULL, 则传回新加入顶点的地址函数 cvgraphaddvtx 将一顶点加入到图中, 并返回定点的索引 [ ] GraphRemoveVtx 通过索引从图中删除一顶点 164

165 int cvgraphremovevtx( CvGraph* graph, int index ); graph 图 vtx_idx 被珊顶点的索引函数 cvgraphremoveaddvtx 从图中删除一顶点, 连同删除含有此顶点的边 如果输入的顶点不属于该图的话, 将报告删除出错 ( 不存在而无法删除 ) 返回值为被删除的边数, 如果顶点不属于该图的话, 返回 -1 [ ] GraphRemoveVtxByPtr 通过指针从图中删除一顶点 int cvgraphremovevtxbyptr( CvGraph* graph, CvGraphVtx* vtx ); graph vtx 图 指向被删除的边的指针 函数 cvgraphremovevtxbyptr 从图中删除一顶点, 连同删除含有此顶点的边 如果输入的顶点不属于该图的话, 将报告删除出错 ( 不存在而无法删除 ) 返回值为被删除的边数, 如果顶点不属于该图的话, 返回 -1 [ ] GetGraphVtx 通过索引值查找图的相应顶点 CvGraphVtx* cvgetgraphvtx( CvGraph* graph, int vtx_idx ); graph 图 vtx_idx 定点的索引值函数 cvgetgraphvtx 通过索引值查找对应的顶点, 并返回指向该顶点的指针, 如果不存在则返回 NULL. [ ] GraphVtxIdx 返回定点相应的索引值 165

166 int cvgraphvtxidx( CvGraph* graph, CvGraphVtx* vtx ); graph vtx 图 指向顶点的指针 函数 cvgraphvtxidx 返回与顶点相应的索引值 [ ] GraphAddEdge 通过索引值在图中加入一条边 int cvgraphaddedge( CvGraph* graph, int start_idx, int end_idx, inserted_edge=null ); const CvGraphEdge* edge=null, CvGraphEdge** graph 图 start_idx 边的起始顶点的索引值 end_idx 边的尾部顶点的索引值 ( 对于无向图, 参数的次序无关紧要, 即 :start_idx 和 end_idx 可互为起始顶点和尾部顶点 ) edge 可选的输入参数, 初始化边的数据 inserted_edge 可选的输出参数, 包含被插入的边的地址 函数 cvgraphaddedge 连接两特定的顶点 如果该边成功地加入到图中, 返回 1; 如果连接两顶点的边已经存在, 返回 0; 如果顶点没被发现 ( 不存在 ) 或者起始顶点和尾部顶点是同一个定点, 或其他特殊情况, 返回 -1 如果是后者 ( 即 : 返回值为负 ), 函数默认的报告一个错误 [ ] GraphAddEdgeByPtr 通过指针在图中加入一条边 int cvgraphaddedgebyptr( CvGraph* graph, CvGraphVtx* start_vtx, CvGraphVtx* end_vtx, 166

167 inserted_edge=null ); const CvGraphEdge* edge=null, CvGraphEdge** graph 图 start_vtx 指向起始顶点的指针 end_vtx 指向尾部顶点的指针 对于无向图来说, 顶点参数的次序无关紧要 edge 可选的输入参数, 初始化边的数据 inserted_edge 可选的输出参数, 包含被插入的边的地址 函数 cvgraphaddedge 连接两特定的顶点 如果该边成功地加入到图中, 返回 1; 如果连接两顶点的边已经存在, 返回 0; 如果顶点没被发现 ( 不存在 ) 或者起始顶点和尾部顶点是同一个定点, 或其他特殊情况, 返回 -1 如果是后者 ( 即 : 返回值为负 ), 函数默认的报告一个错误 [ ] GraphRemoveEdge 通过索引值从图中删除顶点 void cvgraphremoveedge( CvGraph* graph, int start_idx, int end_idx ); graph 图 start_idx 起始顶点的索引值 end_idx 尾部顶点的索引值 对于无向图来说, 顶点参数的次序无关紧要 函数 cvgraphremoveedge 删除连接两特定顶点的边 若两顶点并没有相连接 ( 即 : 不存在由这两个顶点连接的边 ), 函数什么都不做 [ ] GraphRemoveEdgeByPtr 通过指针从图中删除边 void cvgraphremoveedgebyptr( CvGraph* graph, CvGraphVtx* start_vtx, CvGraphVtx* end_vtx ); graph 图 167

168 start_vtx 指向起始顶点的指针 end_vtx 指向尾部顶点的指针 对于无向图来说, 顶点参数的次序无关紧要 函数 cvgraphremoveedgebyptr 删除连接两特定顶点的边 若两顶点并没有相连接 ( 即 : 不存在由这两个顶点连接的边 ), 函数什么都不做 [ ] FindGraphEdge 通过索引值在图中查找相应的边 CvGraphEdge* cvfindgraphedge( const CvGraph* graph, int start_idx, int end_idx ); #define cvgraphfindedge cvfindgraphedge graph 图 start_idx 起始顶点的索引值 end_idx 尾部顶点的索引值 对于无向图来说, 顶点参数的次序无关紧要函数 cvfindgraphedge 查找与两特定顶点相对应的边, 并返回指向该边的指针 如果该边不存在, 返回 NULL. [ ] FindGraphEdgeByPtr 通过指针在图中查找相应的边 CvGraphEdge* cvfindgraphedgebyptr( const CvGraph* graph, const CvGraphVtx* start_vtx, const CvGraphVtx* end_vtx ); #define cvgraphfindedgebyptr cvfindgraphedgebyptr graph 图 start_vtx 指向起始顶点的指针 end_vtx 指向尾部顶点的指针 对于无向图来说, 顶点参数的次序无关紧要 函数 cvfindgraphedgebyptr 查找与两特定顶点相对应的边, 并返回指向该边的指针 如果该边不存在, 返回 NULL [ ] 168

169 GraphEdgeIdx 返回与该边相应的索引值 int cvgraphedgeidx( CvGraph* graph, CvGraphEdge* edge ); graph edge 图 指向该边的指针 函数 cvgraphedgeidx 返回与边对应的索引值 [ ] GraphVtxDegree ( 通过索引值 ) 统计与顶点相关联的边数 int cvgraphvtxdegree( const CvGraph* graph, int vtx_idx ); graph 图 vtx_idx 顶点对应的索引值函数 cvgraphvtxdegree 返回与特定顶点相关联的边数, 包括以该顶点为起始顶点的和尾部顶点的 统计边数, 可以适用下列代码 : CvGraphEdge* edge = vertex->first; int count = 0; while( edge ) edge = CV_NEXT_GRAPH_EDGE( edge, vertex ); count++; } 宏 CV_NEXT_GRAPH_EDGE(edge, vertex) 返回依附于该顶点的下一条边 [ ] GraphVtxDegreeByPtr ( 通过指针 ) 统计与顶点相关联的边数 int cvgraphvtxdegreebyptr( const CvGraph* graph, const CvGraphVtx* vtx ); graph 图 169

170 vtx 顶点对应的指针 函数 cvgraphvtxdegreebyptr 返回与特定顶点相关联的边数, 包括以该顶点为起始顶点的和尾部顶点的 [ ] ClearGraph 删除图 void cvcleargraph( CvGraph* graph ); graph 图 函数 cvcleargraph 删除该图的所有顶点和边 时间复杂度为 O(1). [ ] CloneGraph 克隆图 CvGraph* cvclonegraph( const CvGraph* graph, CvMemStorage* storage ); graph 待拷贝的图 storage 容器, 存放拷贝函数 cvclonegraph 创建图的完全拷贝 如果顶点和边含有指向外部变量的指针, 那么图和它的拷贝共享这些指针 在新的图中, 顶点和边可能存在不同, 因为函数重新分割了顶点和边的点集 [ ] CvGraphScanner 图的遍历 typedef struct CvGraphScanner CvGraphVtx* vtx; /* current graph vertex (or current edge origin) */ CvGraphVtx* dst; /* current graph edge destination vertex */ CvGraphEdge* edge; /* current edge */ CvGraph* graph; /* the graph */ CvSeq* stack; /* the graph vertex stack */ int index; /* the lower bound of certainly visited vertices */ 170

171 int mask; /* event mask */ } CvGraphScanner; 结构 cvgraphscanner 深度遍历整个图 函数的相关讨论如下 ( 看 :StartScanGraph) [ ] StartScanGraph 创建一结构, 用来对图进行深度遍历 CvGraphScanner* cvcreategraphscanner( CvGraph* graph, CvGraphVtx* vtx=null, int mask=cv_graph_all_items ); graph vtx mask 图开始遍历的 ( 起始 ) 顶点 如果为 NULL, 便利就从第一个顶点开始 ( 指 : 顶点序列中, 具有最小索引值的顶点 ) 事件掩码 (event mask) 代表用户感兴趣的事件 ( 此时函数 cvnextgraphitem 将控制返回给用户 ) 这个只可能是 CV_GRAPH_ALL_ITEMS ( 如果用户对所有的事件都感兴趣的话 ) 或者是下列标志的组合 : CV_GRAPH_VERTEXT -- 在第一次被访问的顶点处停下 CV_GRAPH_TREE_EDGE -- 在 tree edge 处停下 (tree edge 指连接最后被访问的顶点与接下来被访问的顶点的边 ) CV_GRAPH_BACK_EDGE -- 在 back edge 处停下 (back edge 指连接最后被访问的顶点与其在搜索树中祖先的边 ) CV_GRAPH_FORWARD_EDGE -- 在 forward edge 处停下 (forward edge 指连接最后被访问的顶点与其在搜索树中后裔的边 ) CV_GRAPH_CROSS_EDGE -- 在 cross edge 处停下 (cross edge 指连接不同搜索树中或同一搜索树中不同分支的边. 只有在有向图中, 才存在着一概念 ) CV_GRAPH_ANY_EDGE -- 在 any edge 处停下 (any edge 指任何边, 包括 tree edge, back edge, forward edge, cross edge) CV_GRAPH_NEW_TREE -- 在每一个新的搜索树开始处停下 首先遍历从起始顶点开始可以访问到的顶点和边, 然后查找搜索图中访问不到的顶点或边并恢复遍历 在开始遍历一颗新的树时 ( 包括第一次调用 cvnextgraphitem 时的树 ), 产生 CV_GRAPH_NEW_TREE 事件 函数 cvcreategraphscanner 创建一结构用来深度遍历搜索树 函数 cvnextgraphitem 要使用该初始化了的结构 -- 层层遍历的过程 [ ] NextGraphItem 171

172 逐层遍历整个图 int cvnextgraphitem( CvGraphScanner* scanner ); scanner 图的遍历状态 被此函数更新 函数 cvnextgraphitem 遍历整个图, 直到用户感兴趣的事件发生 ( 即 : 调用 cvcreategraphscanner 时, mask 对应的事件 ) 或遍历结束 在前面一种情况下, 函数返回参数 mask 相应的事件, 当再次调用函数时, 恢复遍历 ) 在后一种情况下, 返回 CV_GRAPH_OVER(-1) 当 mask 相应的事件为 CV_GRAPH_BACKTRACKING 或 CV_GRAPH_NEW_TEEE 时, 当前正在被访问的顶点被存放在 scanner->vtx 中 如果事件与边 edge 相关, 那幺 edge 本身被存放在 scanner->edge, 该边的起始顶点存放在 scanner->vtx 中, 尾部节点存放在 scanner->dst 中 [ ] ReleaseGraphScanner 完成图地遍历过程 void cvreleasegraphscanner( CvGraphScanner** scanner ); scanner 指向遍历器的指针. 函数 cvgraphscanner 完成图的遍历过程, 并释放遍历器的状态 [ ] 树 [ ] CV_TREE_NODE_FIELDS 用于树结点类型声明的 ( 助手 ) 宏 #define CV_TREE_NODE_FIELDS(node_type) \ int flags; /* micsellaneous flags */ \ int header_size; /* size of sequence header */ \ struct node_type* h_prev; /* previous sequence */ \ struct node_type* h_next; /* next sequence */ \ struct node_type* v_prev; /* 2nd previous sequence */ \ struct node_type* v_next; /* 2nd next sequence */ 172

173 宏 CV_TREE_NODE_FIELDS() 用来声明一层次性结构, 例如 CvSeq -- 所有动态结构的基本类型 如果树的节点是由该宏所声明的, 那么就可以使用 ( 该部分的 ) 以下函数对树进行相关操作 [ ] CvTreeNodeIterator 打开现存的存储结构或者创建新的文件存储结构 typedef struct CvTreeNodeIterator const void* node; int level; int max_level; } CvTreeNodeIterator; 结构 CvTreeNodeIterator 用来对树进行遍历 该树的节点是由宏 CV_TREE_NODE_FIELDS 声明 [ ] InitTreeNodeIterator 用来初始化树结点的迭代器 void cvinittreenodeiterator( CvTreeNodeIterator* tree_iterator, const void* first, int max_level ); tree_iterator 初始化了的迭代器 first ( 开始 ) 遍历的第一个节点 max_level 限制对树进行遍历的最高层 ( 即 : 第 max_level 层 )( 假设第一个节点所在的层为第一层 ) 例如:1 指的是遍历第一个节点所在层,2 指的是遍历第一层和第二层函数 cvinittreenodeiterator 用来初始化树的迭代器 [ ] NextTreeNode 返回当前节点, 并将迭代器 iterator 移向当前节点的下一个节点 void* cvnexttreenode( CvTreeNodeIterator* tree_iterator ); 173

174 tree_iterator 初始化了的迭代器函数 cvnexttreenode 返回当前节点并且更新迭代器 (iterator) -- 并将 iterator 移向 ( 当前节点 ) 下一个节点 换句话说, 函数的行为类似于表达式 *p++ ( 通常的 C 指针或 C++ 集合迭代器 ) 如果没有更多的节点 ( 即 : 当前节点为最后的节点 ), 则函数返回值为 NULL. [ ] PrevTreeNode 返回当前节点, 并将迭代器 iterator 移向当前节点的前一个节点 void* cvprevtreenode( CvTreeNodeIterator* tree_iterator ); tree_iterator 初始化了的迭代器函数 cvprevtreenode 返回当前节点并且更新迭代器 (iterator) -- 并将 iterator 移向 ( 当前节点的 ) 前一个节点 换句话说, 函数的行为类似于表达式 *p-- ( 通常的 C 指针或 C++ 集合迭代器 ) 如果没有更多的节点 ( 即 : 当前节点为头节点 ), 则函数返回值为 NULL. [ ] TreeToNodeSeq 将所有的节点指针 ( 即 : 指向树结点的指针 ) 收集到线性表 sequence 中 CvSeq* cvtreetonodeseq( const void* first, int header_size, CvMemStorage* storage ); first 初始树结点 header_size 线性表的表头大小, 大小通常为 sizeof(cvseq) 函数 cvtreetonodeseq 将树的节点指针挨个的存放到线性表中 存放的顺序以深度为先 [ ] InsertNodeIntoTree 将新的节点插入到树中 void cvinsertnodeintotree( void* node, void* parent, void* frame ); node 待插入的节点 parent 树中的父节点 ( 即 : 含有子节点的节点 ) 174

175 frame 顶部节点 如果节点 parent 等同于节点 frame, 则将节点的域 v_prev 设为 NULL 而不是 parent. 函数 cvinsertnodeintotree 将另一个节点插入到树中 函数不分配任何内存, 仅仅修改树节点的连接关系 RemoveNodeFromTree 从树中删除节点 void cvremovenodefromtree( void* node, void* frame ); node frame 待删除的节点 顶部节点 如果 node->v.prev = NULL 且 node->h.prev = NULL, 则将 frame->v.next 设为 node->h.next 函数 cvremovenodefromtree 从树中删除节点 它不会释放任何内存, 仅仅修改树中节点的连接关系 4.Cxcore 绘图函数 绘图函数作用于任何象素深度的矩阵 / 图像. Antialiasing 技术只能在 8 位图像上实现. 所有的函数包括彩色图像的色彩参数 ( 色彩参数是指 rgb 它是由宏 CV_RGB 或 cvscalar 函数构成 ) 和灰度图像的亮度 如果一幅绘制图形部分或全部位于图像之外, 那么对它先做裁剪 对于彩色图像正常的色彩通道是 B( 蓝 ),G( 绿 ),R( 红 ).. 如果需要其它的色彩, 可以通过 cvscalar 中的特殊色彩通道构造色彩, 或者在绘制图像之前或之后使用 cvcvtcolor 或者 cvtransform 来转换 [ ] 曲线与形状 CV_RGB 创建一个色彩值. 175

176 #define CV_RGB( r, g, b ) cvscalar( (b), (g), (r) ) [ ] Line 绘制连接两个点的线段 void cvline( CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像 pt1 线段的第一个端点 pt2 线段的第二个端点 color 线段的颜色 thickness 线段的粗细程度 line_type 线段的类型 8 (or 0) - 8-connected line(8 邻接 ) 连接线 4-4-connected line(4 邻接 ) 连接线 CV_AA - antialiased 线条 shift 坐标点的小数点位数 函数 cvline 在图像中的点 1 和点 2 之间画一条线段 线段被图像或感兴趣的矩形 (ROI rectangle) 所裁剪 对于具有整数坐标的 non-antialiasing 线条, 使用 8- 连接或者 4- 连接 Bresenham 算法 画粗线条时结尾是圆形的 画 antialiased 线条使用高斯滤波 要指定线段颜色, 用户可以使用使用宏 CV_RGB( r, g, b ) [ ] Rectangle 绘制简单 指定粗细或者带填充的矩形 void cvrectangle( CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像. 176

177 pt1 矩形的一个顶点 pt2 矩形对角线上的另一个顶点 color 线条颜色 (RGB) 或亮度 ( 灰度图像 )(grayscale image) thickness 组成矩形的线条的粗细程度 取负值时 ( 如 CV_FILLED) 函数绘制填充了色彩的矩形 line_type 线条的类型 见 cvline 的描述 shift 坐标点的小数点位数 函数 cvrectangle 通过对角线上的两个顶点绘制矩形 [ ] Circle 绘制圆形 void cvcircle( CvArr* img, CvPoint center, int radius, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像 center 圆心坐标 radius 圆形的半径 color 线条的颜色 thickness 如果是正数, 表示组成圆的线条的粗细程度 否则, 表示圆是否被填充 line_type 线条的类型 见 cvline 的描述 shift 圆心坐标点和半径值的小数点位数 函数 cvcircle 绘制或填充一个给定圆心和半径的圆 圆被感兴趣矩形所裁剪 若指定圆的颜色, 可以使用宏 CV_RGB ( r, g, b ) [ ] Ellipse 177

178 绘制椭圆圆弧和椭圆扇形 void cvellipse( CvArr* img, CvPoint center, CvSize axes, double angle, double start_angle, double end_angle, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像 center 椭圆圆心坐标 axes 轴的长度 angle 偏转的角度 start_angle 圆弧起始角的角度. end_angle 圆弧终结角的角度 color 线条的颜色 thickness 线条的粗细程度 line_type 线条的类型, 见 CVLINE 的描述 shift 圆心坐标点和数轴的精度 函数 cvellipse 用来绘制或者填充一个简单的椭圆弧或椭圆扇形 圆弧被 ROI 矩形所忽略 反走样弧线和粗弧线使用线性分段近似值 所有的角都是以角度的形式给定的 下面的图片将解释这些参数的含义 Parameters of Elliptic Arc 178

179 [ ] EllipseBox 使用一种简单的方式来绘制椭圆圆弧和椭圆扇形 void cvellipsebox( CvArr* img, CvBox2D box, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像 box 绘制椭圆圆弧所需要的外界矩形. thickness 分界线线条的粗细程度 line_type 分界线线条的类型, 见 CVLINE 的描述 shift 椭圆框顶点坐标的精度 The function cvellipsebox draws a simple or thick ellipse outline, or fills an ellipse. The functions provides a convenient way to draw an ellipse approximating some shape; that is what cvcamshift and cvfitellipse do. The ellipse drawn is clipped by ROI rectangle. A piecewise-linear approximation is used for antialiased arcs and thick arcs. [ ] FillPoly 填充多边形内部 179

180 void cvfillpoly( CvArr* img, CvPoint** pts, int* npts, int contours, CvScalar color, int line_type=8, int shift=0 ); img 图像 pts 指向多边形的数组指针 npts 多边形的顶点个数的数组 contours 组成填充区域的线段的数量 color 多边形的颜色 line_type 组成多边形的线条的类型 shift 顶点坐标的小数点位数 函数 cvfillpoly 用于一个单独被多边形轮廓所限定的区域内进行填充 函数可以填充复杂的区域, 例如, 有漏洞的区域和有交叉点的区域等等 [ ] FillConvexPoly 填充凸多边形 void cvfillconvexpoly( CvArr* img, CvPoint* pts, int npts, CvScalar color, int line_type=8, int shift=0 ); img 图像 pts 指向单个多边形的指针数组 npts 多边形的顶点个数 color 多边形的颜色 line_type 组成多边形的线条的类型 参见 cvline shift 顶点坐标的小数点位数 180

181 函数 cvfillconvexpoly 填充凸多边形内部 这个函数比函数 cvfillpoly 更快 它除了可以填充凸多边形区域还可以填充任何的单调多边形 例如 : 一个被水平线 ( 扫描线 ) 至多两次截断的多边形 [ ] PolyLine 绘制简单线段或折线 void cvpolyline( CvArr* img, CvPoint** pts, int* npts, int contours, int is_closed, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 图像 pts 折线的顶点指针数组 npts 折线的定点个数数组 也可以认为是 pts 指针数组的大小 contours 折线的线段数量 is_closed 指出多边形是否封闭 如果封闭, 函数将起始点和结束点连线 color 折线的颜色 thickness 线条的粗细程度 line_type 线段的类型 参见 cvline shift 顶点的小数点位数 函数 cvpolyline 绘制一个简单直线或折线 [ ] 文本 [ ] InitFont 初始化字体结构体 void cvinitfont( CvFont* font, int font_face, double hscale, double vscale, double shear=0, int thickness=1, int line_type=8 ); 181

182 font 被初始化的字体结构体 font_face 字体名称标识符 只是 Hershey 字体集 ( ) 的一个子集得到支持 CV_FONT_HERSHEY_SIMPLEX - 正常大小无衬线字体 CV_FONT_HERSHEY_PLAIN - 小号无衬线字体 CV_FONT_HERSHEY_DUPLEX - 正常大小无衬线字体 ( 比 CV_FONT_HERSHEY_SIMPLEX 更复杂 ) CV_FONT_HERSHEY_COMPLEX - 正常大小有衬线字体 CV_FONT_HERSHEY_TRIPLEX - 正常大小有衬线字体 ( 比 CV_FONT_HERSHEY_COMPLEX 更复杂 ) CV_FONT_HERSHEY_COMPLEX_SMALL - CV_FONT_HERSHEY_COMPLEX 的小译本 CV_FONT_HERSHEY_SCRIPT_SIMPLEX - 手写风格字体 CV_FONT_HERSHEY_SCRIPT_COMPLEX - 比 CV_FONT_HERSHEY_SCRIPT_SIMPLEX 更复杂 这个参数能够由一个值和可选择的 CV_FONT_ITALIC 字体标记合成, 就是斜体字 hscale 字体宽度 如果等于 1.0f, 字符的宽度是最初的字体宽度 如果等于 0.5f, 字符的宽度是最初的字体宽度的一半 vscale 字体高度 如果等于 1.0f, 字符的高度是最初的字体高度 如果等于 0.5f, 字符的高度是最初的字体高度的一半 shear 字体的斜度 当值为 0 时, 字符不倾斜 ; 当值为 1.0f 时, 字体倾斜 45 度, 等等 厚度让字母着重显示 函数 cvline 用于绘制字母 thickness 字体笔划的粗细程度 line_type 字体笔划的类型, 参见 cvline 函数 cvinitfont 初始化字体结构体, 字体结构体可以被传递到文字显示函数中 [ ] PutText 在图像中显示文本字符串 void cvputtext( CvArr* img, const char* text, CvPoint org, const CvFont* font, CvScalar color ); img text org 输入图像 要显示的字符串 第一个字符左下角的坐标 182

183 font color 字体结构体 文本的字体颜色 函数 cvputtext 将具有指定字体的和指定颜色的文本加载到图像中 加载到图像中的文本被感兴趣的矩形框 (ROI rectangle) 剪切 不属于指定字体库的字符用矩形字符替代显示 [ ] GetTextSize 获得字符串的宽度和高度 void cvgettextsize( const char* text_string, const CvFont* font, CvSize* text_size, int* baseline ); font 字体结构体 text_string 输入字符串 text_size 合成字符串的字符的大小 文本的高度不包括基线以下的部分 baseline 相对于文字最底部点的基线的 Y 坐标 函数 cvgettextsize 是用于在指定字体时计算字符串的绑定区域 (binding rectangle) [ ] 点集和轮廓 [ ] DrawContours 在图像中绘制外部和内部的轮廓 void cvdrawcontours( CvArr *img, CvSeq* contour, CvScalar external_color, CvScalar hole_color, int max_level, int thickness=1, int line_type=8, CvPoint offset=cvpoint(0,0) ); img 用以绘制轮廓的图像 和其他绘图函数一样, 边界图像被感兴趣区域 (ROI) 所剪切 contour 指针指向第一个轮廓 external_color 外层轮廓的颜色 hole_color 183

184 内层轮廓的颜色 max_level 绘制轮廓的最大等级 如果等级为 0, 绘制单独的轮廓 如果为 1, 绘制轮廓及在其后的相同的级别下轮廓 如果值为 2, 所有的轮廓 如果等级为 2, 绘制所有同级轮廓及所有低一级轮廓, 诸此种种 如果值为负数, 函数不绘制同级轮廓, 但会升序绘制直到级别为 abs(max_level)-1 的子轮廓 thickness 绘制轮廓时所使用的线条的粗细度 如果值为负 (e.g. =CV_FILLED), 绘制内层轮廓 line_type 线条的类型 参考 cvline. offset 按照给出的偏移量移动每一个轮廓点坐标. 当轮廓是从某些感兴趣区域 (ROI) 中提取的然后需要在运算中考虑 ROI 偏移量时, 将会用到这个参数 当 thickness>=0, 函数 cvdrawcontours 在图像中绘制轮廓, 或者当 thickness<0 时, 填充轮廓所限制的区域 #include "cv.h" #include "highgui.h" int main( int argc, char** argv ) IplImage* src; // 第一条命令行参数确定了图像的文件名 if( argc == 2 && (src=cvloadimage(argv[1], 0))!= 0) IplImage* dst = cvcreateimage( cvgetsize(src), 8, 3 ); CvMemStorage* storage = cvcreatememstorage(0); CvSeq* contour = 0; cvthreshold( src, src, 1, 255, CV_THRESH_BINARY ); cvnamedwindow( "Source", 1 ); cvshowimage( "Source", src ); cvfindcontours( src, storage, &contour, sizeof(cvcontour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); cvzero( dst ); for( ; contour!= 0; contour = contour->h_next ) CvScalar color = CV_RGB( rand()&255, rand()&255, rand()&255 ); /* 用 1 替代 CV_FILLED 所指示的轮廓外形 */ 184

185 } cvdrawcontours( dst, contour, color, color, -1, CV_FILLED, 8 ); } } cvnamedwindow( "Components", 1 ); cvshowimage( "Components", dst ); cvwaitkey(0); 在样本中用 1 替代 CV_FILLED 以指示的得到外形 ( 注意 : 在 cvfindcontours 中参数为 CV_CHAIN_CODE 时,cvDrawContours 用 CV_FILLED 时不会画出任何图形 ) [ ] InitLineIterator 初始化直线迭代器 int cvinitlineiterator( const CvArr* image, CvPoint pt1, CvPoint pt2, CvLineIterator* line_iterator, int connectivity=8, int left_to_right=0 ); img 用以获取直线的图像 pt1 线段的第一个端点 pt2 线段的第二个端点 line_iterator 指向直线迭代状态结构体的指针 connectivity 直线的邻接方式,4 邻接或者 8 邻接 left_to_right 标志值, 指出扫描直线是从 pt1 和 pt2 外面最左边的点扫描到最右边的点 (left_to_right 0), 还是按照指定的顺序, 从 pt1 到 pt2(left_to_right=0) 函数 cvinitlineiterator 初始化直线迭代器并返回两个端点间点的数目 两个端点都必须在图像内部 在迭代器初始化以后, 所有的在连接两个终点的栅栏线上的点, 可以通过访问 CV_NEXT_LINE_POINT 点的方式获得 在线上的这些点使用 4- 邻接或者 8- 邻接的 Bresenham 算法计算得到 例 : 使用直线迭代来计算沿着彩色线上的点的像素值 185

186 CvScalar sum_line_pixels( IplImage* image, CvPoint pt1, CvPoint pt2 ) CvLineIterator iterator; int blue_sum = 0, green_sum = 0, red_sum = 0; int count = cvinitlineiterator( image, pt1, pt2, &iterator, 8, 0 ); for( int i = 0; i < count; i++ ) blue_sum += iterator.ptr[0]; green_sum += iterator.ptr[1]; red_sum += iterator.ptr[2]; CV_NEXT_LINE_POINT(iterator); /* print the pixel coordinates: demonstrates how to calculate the coordinates */ int offset, x, y; /* assume that ROI is not set, otherwise need to take it into account. */ offset = iterator.ptr - (uchar*)(image->imagedata); y = offset/image->widthstep; x = (offset - y*image->widthstep)/(3*sizeof(uchar) /* size of pixel */); printf("(%d,%d)\n", x, y ); } } return cvscalar( blue_sum, green_sum, red_sum ); } [ ] ClipLine 剪切图像矩形区域内部的直线 int cvclipline( CvSize img_size, CvPoint* pt1, CvPoint* pt2 ); img_size 图像的大小 pt1 线段的第一个端点, 会被函数修改 pt2 线段的第二个端点, 会被函数修改 186

187 函数 cvclipline 计算线段完全在图像中的一部分 如果线段完全在图像中, 返回 0, 否则返回 1 [ ] Ellipse2Poly 用折线逼近椭圆弧 int cvellipse2poly( CvPoint center, CvSize axes, int angle, int arc_start, int arc_end, CvPoint* pts, int delta ); center 弧线的中心 axes 弧线的 Half-sizes 参见下图 angle 椭圆的旋转角度 (Rotation angle), 参见下图 start_angle 椭圆的 Starting angle, 参见下图 end_angle 椭圆的 Ending angle, 参见下图 pts 坐标点矩阵数组, 由本函数填充 delta 与下一条折线定点的夹角, 近似精度 故, 得到的点数最大为 ceil((end_angle - start_angle)/delta) + 1 函数 cvellipse2poly 计算给定的椭圆弧的逼近折线的顶点, 被 cvellipse 使用 187

188 5.Cxcore 数据保存和运行时类型信息 文件存储 CvFileStorage 文件存储结构 typedef struct CvFileStorage... // hidden fields } CvFileStorage; 构造函数 CvFileStorage 是将磁盘上存储的文件关联起来的 黑匣子 在下列函数描述中利用 CvFileStorage 作为输入, 允许存储或载入各种格式数据组成的层次集合, 这些数据由标量值 (scalar ), 或者 CXCore 对象 ( 例如矩阵, 序列, 图表 ) 和用户自定义对象 CXCore 能将数据读入或写入 XML ( or YAML ( 格式. 下面这个例子是利用 CXCore 函数将 3 3 单位浮点矩阵存入 XML 和 YAML 文档 XML: <?xml version="1.0"> <opencv_storage> <A type_id="opencv-matrix"> <rows>3</rows> <cols>3</cols> <dt>f</dt> <data> </data> </A> </opencv_storage> YAML: %YAML:1.0 A:!!opencv-matrix rows: 3 cols: 3 dt: f data: [ 1., 0., 0., 0., 1., 0., 0., 0., 1.] 188

189 从例子中可以看到, XML 是用嵌套标签来表现层次, 而 YAML 用缩排来表现 ( 类似于 Python 语言 ) 相同的 CXCore 函数也能够在这两种格式下读写数据, 特殊的格式决定了文件的扩展名,.xml 是 XML 的扩展名,.yml 或.yaml 是 YAML 的扩展名 CvFileNode 文件存储器节点 /* 文件节点类型 */ #define CV_NODE_NONE 0 #define CV_NODE_INT 1 #define CV_NODE_INTEGER CV_NODE_INT #define CV_NODE_REAL 2 #define CV_NODE_FLOAT CV_NODE_REAL #define CV_NODE_STR 3 #define CV_NODE_STRING CV_NODE_STR #define CV_NODE_REF 4 /* not used */ #define CV_NODE_SEQ 5 #define CV_NODE_MAP 6 #define CV_NODE_TYPE_MASK 7 /* 可选标记 */ #define CV_NODE_USER 16 #define CV_NODE_EMPTY 32 #define CV_NODE_NAMED 64 #define CV_NODE_TYPE(tag) ((tag) & CV_NODE_TYPE_MASK) #define CV_NODE_IS_INT(tag) (CV_NODE_TYPE(tag) == CV_NODE_INT) #define CV_NODE_IS_REAL(tag) (CV_NODE_TYPE(tag) == CV_NODE_REAL) #define CV_NODE_IS_STRING(tag) (CV_NODE_TYPE(tag) == CV_NODE_STRING) #define CV_NODE_IS_SEQ(tag) (CV_NODE_TYPE(tag) == CV_NODE_SEQ) #define CV_NODE_IS_MAP(tag) (CV_NODE_TYPE(tag) == CV_NODE_MAP) #define CV_NODE_IS_COLLECTION(tag) (CV_NODE_TYPE(tag) >= CV_NODE_SEQ) #define CV_NODE_IS_FLOW(tag) (((tag) & CV_NODE_FLOW)!= 0) #define CV_NODE_IS_EMPTY(tag) (((tag) & CV_NODE_EMPTY)!= 0) #define CV_NODE_IS_USER(tag) (((tag) & CV_NODE_USER)!= 0) #define CV_NODE_HAS_NAME(tag) (((tag) & CV_NODE_NAMED)!= 0) #define CV_NODE_SEQ_SIMPLE 256 #define CV_NODE_SEQ_IS_SIMPLE(seq) (((seq)->flags & CV_NODE_SEQ_SIMPLE)!= 0) 189

190 typedef struct CvString int len; char* ptr; } CvString; /* 所有已读存储在文件元素的关键字被存储在 hash 表中, 这样可以加速查找操作 */ typedef struct CvStringHashNode unsigned hashval; CvString str; struct CvStringHashNode* next; } CvStringHashNode; /* 文件存储器的基本元素是 - 标量或集合 */ typedef struct CvFileNode int tag; struct CvTypeInfo* info; /* 类型信息 ( 只能用于用户自定义对象, 对于其它对象它为 0) */ union double f; /* 浮点数 */ int i; /* 整形数 */ CvString str; /* 字符文本 */ CvSeq* seq; /* 序列 ( 文件节点的有序集合 ) */ struct CvMap* map; /* 图表 ( 指定的文件节点的集合 ) */ } data; } CvFileNode; 这个构造函数只是用于重新找到文件存储器上的数据 ( 例如, 从文件中下载数据 ) 当数据已经写入文件时, 按顺序写入, 只用最小的缓冲完成, 此时没有数据存放在文件存储器 相反, 当从文件中读数据时, 所有文件在内存中像树一样被解析和描绘 树的每一个节点被 CvFileNode 表现出来 文件节点 N 的类型能够通过 CV_NODE_TYPE(N->tag) 被重新找到 一些节点 ( 叶结点 ) 作为变量 : 字符串文本, 整数, 浮点数 其它的文件节点是集合文件节点, 有两个类型集合 : 序列和图表 ( 我们这里使用 YAML 符号, 无论用哪种方法, 对于 XML 符号流也是同样有效 ) 序列 ( 不要与 CvSeq 混淆 ) 是由有序的非指定文件节点 ( 注 : 没有关键字 ) 构成的, 图表是由无序的指定文件节点 ( 注 : 有关键字 ) 构成的 因而, 序列的数据是通 190

191 过索引 (cvgetsepelem) 来存取, 图形的数据是通过名字 (cvgetfilenodebyname) 来存取下表描述不同类型的节点 : Type CV_NODE_TYPE(node->tag) Value Integer CV_NODE_INT node->data.i Floating-point CV_NODE_REAL node->data.f Text string CV_NODE_STR node->data.str.ptr Sequence CV_NODE_SEQ node->data.seq Map CV_NODE_MAP node->data.map* 这里不需要直接存取图表内容 ( 顺便说一下 CvMap 是一个隐藏的构造函数 ) 图形中的数据可以用 cvgetfilenodebyname 函数得到, 函数返回指向图表文件节点的指针 一个用户对象是一个标准的类型实例, 例如 CvMat, CvSeq 等, 或者任何一个已注册的类型使用 cvregistertypeinfo 这样的对象最初在文件中表现为一种层级关系,( 像表现 XML 和 YAM 示例文件一样 ) 在文件存储器打开并分析之后 当用户调用 cvread 或 cvreadbyname 函数时那么对象将请求被解析 ( 按照原来的存储方式 ) CvAttrList 属性列表 typedef struct CvAttrList const char** attr; /* NULL- 指向数组对 (attribute_name,attribute_value) 的空指针 */ struct CvAttrList* next; /* 指向下一个属性块的指针 */ } CvAttrList; /* 初始化构造函数 CvAttrList */ inline CvAttrList cvattrlist( const char** attr=null, CvAttrList* next=null ); /* 返回值为属性值, 找不到适合的属性则返回值为 0(NULL)*/ const char* cvattrvalue( const CvAttrList* attr, const char* attr_name ); 在当前版本的属性列表用来传递额外的参数, 在使用 cvwrite 写入自定义数据对象时 除了对象类型说明 (type_id 属性 ) 以外, 它不支持 XML 在标签内的属性 ( 注 : 例如 <A name="test"></a> 不支持 ) OpenFileStorage 打开文件存储器读 / 写数据 191

192 CvFileStorage* cvopenfilestorage( const char* filename, CvMemStorage* memstorage, int flags ); filename 内存中的相关文件的文件名 memstorage 内存中通常存储临时数据和动态结构, 例如 CvSeq 和 CvGraph 如果 memstorage 为空, 将建立和使用一个暂存器 flags 读 / 写选择器 CV_STORAGE_READ - 内存处于读状态 CV_STORAGE_WRITE - 内存处于写状态 函数 cvopenfilestorage 打开文件存储器读写数据, 之后建立文件或继续使用现有的文件 文件扩展名决定读文件的类型 :.xml 是 XML 的扩展名,.yml 或.yaml 是 YAML 的扩展名 该函数的返回指针指向 CvFileStorage 结构 ReleaseFileStorage 释放文件存储单元 void cvreleasefilestorage( CvFileStorage** fs ); fs 双指针指向被关闭的文件存储器 函数 cvreleasefilestorage 关闭一个相关的文件存储器并释放所有的临时内存 只有在内存的 I/O 操作完成后才能关闭文件存储器 写数据 StartWriteStruct 向文件存储器中写数据 void cvstartwritestruct( CvFileStorage* fs, const char* name, int struct_flags, const char* type_name=null, CvAttrList attributes=cvattrlist()); fs 初始化文件存储器 name 被写入的数据结构的名称 在存储器被读取时可以通过名称访问数据结构 struct_flags 有下列两个值 : CV_NODE_SEQ - 被写入的数据结构为序列结构 这样的数据没有名称 192

193 CV_NODE_MAP - 被写入的数据结构为图表结构 这样的数据含有名称 这两个标志符必须被指定一个 CV_NODE_FLOW - 这个可选择标识符只能作用于 YAML 流 被写入的数据结构被看做一个数据流 ( 不是数据块 ), 它更加紧凑, 当结构或数组里的数据是标量时, 推荐用这个标志 type_name 可选参数 - 对象类型名称 如果是 XML 用打开标识符 type_id 属性写入 如果是 YAML 用冒号后面的数据结构名写入,:: 基本上它是伴随用户对象出现的 当读存储器时, 编码类型名通常决定对象类型 ( 见 Cvtypeinfo 和 cvfindtypeinfo) attributes 这个参数当前版本没有使用 函数 cvstartwritestruct 开始写复合的数据结构 ( 数据集合 ) 包括序列或图表, 在结构体中所有的字段 ( 可以是标量和新的结构 ) 被写入后, 需要调用 cvendwritestruct. 该函数能够合并一些对象或写入一些用户对象 ( 参考 CvTypeInfo ) EndWriteStruct 结束数据结构的写操作 void cvendwritestruct( CvFileStorage* fs ); fs 初始化文件存储器 函数 cvendwritestruct 结束普通的写数据操作 WriteInt 写入一个整型值 void cvwriteint( CvFileStorage* fs, const char* name, int value ); fs name value 初始的文件存储器 写入值的名称 如果母结构是一个序列, 把 name 的值置为 NULL 写入的整型值 函数 cvwriteint 将一个单独的整型值 ( 有符号的或无符号的 ) 写入文件存储器 WriteReal 写入一个浮点数 void cvwritereal( CvFileStorage* fs, const char* name, double value ); fs 193

194 name value 文件存储器 写入值的名称 如果父结构是一个序列, 则 name 的值应为 NULL 写入的浮点数 函数 cvwritereal 将一个单精度浮点数 ( 有符号的或无符号的 ) 写入文件存储器 一些特殊的值以特殊的编码表示 : NaN +.Inf -.Inf 表示不是数字表示正无穷表示负无穷 下面的实例展示怎样使用底层写函数存储自定义数据结构 void write_termcriteria( CvFileStorage* fs, const char* struct_name, CvTermCriteria* termcrit ) cvstartwritestruct( fs, struct_name, CV_NODE_MAP, NULL, cvattrlist(0,0)); cvwritecomment( fs, "termination criteria", 1 ); if( termcrit->type & CV_TERMCRIT_ITER ) cvwriteinteger( fs, "max_iterations", termcrit->max_iter ); if( termcrit->type & CV_TERMCRIT_EPS ) cvwritereal( fs, "accuracy", termcrit->epsilon ); cvendwritestruct( fs ); } WriteString 写入文本字符串 void cvwritestring( CvFileStorage* fs, const char* name, const char* str, int quote=0 ); fs name str quote 文件存储器 写入字符串的名称 如果父结构是一个序列,name 的值应为 NULL 写入的文本字符串 194

195 如果不为 0, 不管是否需要引号, 字符串都将被写入引号 如果标识符为 0 只有在需要的情况下写入引号 ( 例如字符串的首位是数字或者空格时候就需要两边加上引号 ) 函数 cvwritestring 将文本字符串写入文件存储器 WriteComment 写入注释 void cvwritecomment( CvFileStorage* fs, const char* comment, int eol_comment ); fs 文件存储器 comment 写入的注释, 注释可以是单行的或者多行的 eol_comment 如果不为 0, 函数将注释加到当前行的后面 如果为 0, 并且是多行注释或者当前行放不下, 那么注释将从新的一行开始 函数 cvwritecomment 将注释写入文件存储器 读内存时注释将被跳过, 它只能被用于调试和查看描述 StartNextStream 打开下一个数据流 void cvstartnextstream( CvFileStorage* fs ); fs 初始化文件存储器 函数 cvstartnextstream 从文件存储器中打开下一个数据流 YAML 和 XML 都支持多数据流 这对连接多个文件和恢复写入的程序很有用 Write 写入用户对象 void cvwrite( CvFileStorage* fs, const char* name, const void* ptr, CvAttrList attributes=cvattrlist() ); fs name ptr 文件存储器 写入对象的名称 如果父结构是一个序列,name 的值为 NULL 定义指针指向对象 195

196 attributes 定义对象的属性, 每种类型都有特别的指定 ( 见讨论 ) 函数 cvwrite 将对象写入文件存储器 首先, 使用 cvtypeof 查找恰当的类型信息 其次写入指定的方法类型信息 属性被用于定制写入程序 下面的属性支持标准类型 ( 所有的 *dt 属性在 cvwriterawdata 中都有相同的格式 ): CvSeq header_dt - 序列首位用户区的描述, 它紧跟在 CvSeq 或 CvChain( 如果是自由序列 ) 或 CvContour( 如果是轮廓或点序列 ) 之后 dt - 序列元素的描述 recursive - 如果属性被引用并且不等于 0 或 false", 则所有的序列树 ( 轮廓 ) 都被存储 ( 注 : 递归存储 ) : CvGraph header_dt - 图表头用户区的描述, 它紧跟在 CvGraph 之后 vertex_dt - 图表顶点用户区的描述 edge_dt - 图表边用户区的描述 ( 注意权重值总是被写入, 所以不需要明确的说明 ) 下面的代码的含义是建立 YAML 文件用来描述 CvFileStorage : #include "cxcore.h" int main( int argc, char** argv ) CvMat* mat = cvcreatemat( 3, 3, CV_32F ); CvFileStorage* fs = cvopenfilestorage( "example.yml", 0, CV_STORAGE_WRITE ); cvsetidentity( mat ); cvwrite( fs, "A", mat, cvattrlist(0,0) ); } cvreleasefilestorage( &fs ); cvreleasemat( &mat ); return 0; WriteRawData 196

197 写入基本数据数组 void cvwriterawdata( CvFileStorage* fs, const void* src, int len, const char* dt ); fs src len dt 文件存储器 指针指向输入数组 写入数组的长度 下面是每一个数组元素说明的格式 : ([count]'u' 'c' 'w' 's' 'i' 'f' 'd'})..., 这些特性与 C 语言的类型相似 : 'u' - 8 位无符号数 'c' - 8 位符号数 'w' - 16 位无符号数 's' - 16 位符号数 'i' - 32 位符号数 'f' - 单精度浮点数 'd' - 双精度浮点数 'r' - 指针 输入的带符号的低 32 位整数 这个类型常被用来存储结构体之间的链接 count 是可选的, 是当前类型的计数器 例如, dt='2if' 是指任意的一个数组元素的结构是 :2 个字节整形数, 后面跟一个单精度浮点数 上面的说明与 iif', '2i1f' 等相同 另外一个例子 :dt='u' 是指一个由类型组成的数组, dt='2d' 是指由两个双精度浮点数构成的数组 函数 cvwriterawdata 将数组写入文件存储器, 数组由单独的数值构成 这个函数也可以用循环调用 cvwriteint 和 cvwritereal 替换, 但是一个单独的函数更加有效 需要说明的是, 那是因为元素没有名字, 把它们写入序列 ( 无名字 ) 比写入图表 ( 有名字关联 ) 速度会快 WriteFileNode 将文件节点写入另一个文件存储器 void cvwritefilenode( CvFileStorage* fs, const char* new_node_name, const CvFileNode* node, int embed ); fs 197

198 文件存储器 new_file_node 在目的文件存储器中设置新的文件节点名 保持现有的文件节点名, 使用 cvgetfilenodename( 节点 ). node 被写入的节点 embed 如果被写入的节点是个集合并且 embed 不为 0, 不建立额外的层次结构 否则所有的节点元素被写入新建的文件节点上 不过需要确定一点的是, 图表元素只被写入图表, 序列元素只被写入序列函数 cvwritefilenode 将一个文件节点的拷贝写入文件存储器可能应用范围是 : 将几个文件存储器合而为一 在 XML 和 YAML 之间变换格式等 读取数据从文件存储器中得到数据有两种步骤 : 首先查找文件节点包括哪些被请求的数据 ; 然后利用手动或者使用自定义 read 方法取得数据 GetRootFileNode 从文件存储器中得到一个高层节点 CvFileNode* cvgetrootfilenode( const CvFileStorage* fs, int stream_index=0 ); fs 初始化文件存储器 stream_index 从零开始计数的基索引 参考 cvstartnextstream. 在通常情况下, 文件中只有一个流, 但是可以拥有多个 函数 cvgetrootfilenode 返回一个高层文件节点 高层节点没有名称, 它们和流相对应, 接连存入文件存储器 如果超出索引范围, 函数返回 NULL 指针, 所以要得到所有高层节点需要反复调用函数 stream_index=0,1,..., 直到返回 NULL 指针 这个函数是在文件存储器中递归寻找的基础方法 GetFileNodeByName 在图表或者文件存储器中查找节点 CvFileNode* cvgetfilenodebyname( const CvFileStorage* fs, const CvFileNode* map, const char* name ); fs map 初始化文件存储器 198

199 name 设置父图表 如果为 NULL, 函数在所有的高层节点 ( 流 ) 中检索, 从第一个开始 设置文件节点名 函数 cvgetfilenodebyname 文件节点通过 name 查找文件节点该节点在图表中被查找, 或者如果指针为 NULL, 那么在内存中的高层文件节点中查找 在图表中或者在序列调用 cvgetseqelem 中使用到这个函数, 这样可能遍历整个文件存储器 为了加速确定某个关键字的多重查询 ( 例如结构数组 ), 可以在 cvgethashedkey 和 cvgetfilenode 之中用到一个 GetHashedKey 返回一个指向已有名称的唯一指针 CvStringHashNode* cvgethashedkey( CvFileStorage* fs, const char* name, int len=-1, int create_missing=0 ); fs 初始化文件存储器 name 设置文字节点名 len 名称的长度 ( 已知 ), 如果值为 -1 长度会被计算出来 create_missing 标识符说明, 是否应该将一个缺省节点的值加入哈希表 函数 cvgethashedkey 返回指向每一个特殊文件节点名的唯一指针 这个指针可以传递给 cvgetfilenode 函数 它比 cvgetfilenodebyname 快, 因为比较指针相对比较字符串快些 观察下面例子 : 用二维图来表示一个点集, 例 : %YAML:1.0 points: - x: 10, y: 10 } - x: 20, y: 20 } - x: 30, y: 30 } #... 因而, 它使用哈希指针 x 和 y" 加速对点的解析 例 : 从一个文件存储器中读取一组的结构 #include "cxcore.h" 199

200 int main( int argc, char** argv ) CvFileStorage* fs = cvopenfilestorage( "points.yaml", 0, CV_STORAGE_READ ); CvStringHashNode* x_key = cvgethashedkey( fs, "x", -1, 1 ); CvStringHashNode* y_key = cvgethashedkey( fs, "y", -1, 1 ); CvFileNode* points = cvgetfilenodebyname( fs, 0, "points" ); if( CV_NODE_IS_SEQ(points->tag) ) CvSeq* seq = points->data.seq; int i, total = seq->total; CvSeqReader reader; cvstartreadseq( seq, &reader, 0 ); for( i = 0; i < total; i++ ) CvFileNode* pt = (CvFileNode*)reader.ptr; #if 1 /* 快变量 */ CvFileNode* xnode = cvgetfilenode( fs, pt, x_key, 0 ); CvFileNode* ynode = cvgetfilenode( fs, pt, y_key, 0 ); assert( xnode && CV_NODE_IS_INT(xnode->tag) && ynode && CV_NODE_IS_INT(ynode->tag)); int x = xnode->data.i; // or x = cvreadint( xnode, 0 ); int y = ynode->data.i; // or y = cvreadint( ynode, 0 ); #elif 1 /* 慢变量 : 不使用 x 值与 y 值 */ CvFileNode* xnode = cvgetfilenodebyname( fs, pt, "x" ); CvFileNode* ynode = cvgetfilenodebyname( fs, pt, "y" ); assert( xnode && CV_NODE_IS_INT(xnode->tag) && ynode && CV_NODE_IS_INT(ynode->tag)); int x = xnode->data.i; // or x = cvreadint( xnode, 0 ); int y = ynode->data.i; // or y = cvreadint( ynode, 0 ); #else /* 最慢的可以轻松使用的变量 */ int x = cvreadintbyname( fs, pt, "x", 0 /* default value */ ); int y = cvreadintbyname( fs, pt, "y", 0 /* default value */ ); #endif CV_NEXT_SEQ_ELEM( seq->elem_size, reader ); printf("%d: (%d, %d)\n", i, x, y ); } } cvreleasefilestorage( &fs ); return 0; } 200

201 请注意, 无论使用那一种方法访问图表, 都比使用序列慢, 例如上面的例子, 如果把数据作为整数对放在在单一数字序列中, 效率会更高 GetFileNode 在图表或者文件存储器中查找节点 CvFileNode* cvgetfilenode( CvFileStorage* fs, CvFileNode* map, const CvStringHashNode* key, int create_missing=0 ); fs 初始化文件存储器 map 设置母图表 如果为 NULL, 函数在所有的高层节点 ( 流 ) 中检索, 如果图表与值都为 NULLs, 函数返回到根节点 - 图表包含高层节点 key 指向节点名的特殊节点, 从 cvgethashedkey 中得到 create_missing 标识符说明, 是否应该将一个缺省节点加入图表 函数 cvgetfilenode 查找一个文件节点 函数能够插入一个新的节点, 当它不在图表中时 GetFileNodeName 返回文件节点名 const char* cvgetfilenodename( const CvFileNode* node ); node 初始化文件节点 函数 cvgetfilenodename 返回文件节点名或返回 NULL( 如果文件节点没有名称或者 node 为 NULL ReadInt 从文件节点中得到整形值 int cvreadint( const CvFileNode* node, int default_value=0 ); node 初始化文件节点 default_value 如果 node 为 NULL, 返回一个值 201

202 函数 cvreadint 从文件节点中返回整数 如果文件节点为 NULL, default_value 被返回 另外如果文件节点有类型 CV_NODE_INT, 则 node->data.i 被返回 如果文件节点有类型 CV_NODE_REAL, 则 node->data.f 被修改成整数后返回 其他的情况是则结果不确定 ReadIntByName 查找文件节点返回它的值 int cvreadintbyname( const CvFileStorage* fs, const CvFileNode* map, const char* name, int default_value=0 ); fs 初始化文件存储器 map 设置父图表 如果为 NULL, 函数在所有的高层节点 ( 流 ) 中检索 name 设置节点名 default_value 如果文件节点为 NULL, 返回一个值 函数 cvreadintbyname 是 cvgetfilenodebyname 和 cvreadint 的简单重叠. ReadReal 从文件节点中得到浮点形值 double cvreadreal( const CvFileNode* node, double default_value=0. ); node 初始化文件节点 default_value 如果 node 为 NULL, 返回一个值 函数 cvreadreal 从文件节点中返回浮点形值 如果文件节点为 NULL, default_value 被返回 ( 这样就不用检查 cvgetfilenode 返回的指针是否为空了 ) 另外如果文件节点有类型 CV_NODE_REAL, 则 node->data.f 被返回 如果文件节点有类型 CV_NODE_INT, 则 node->data.i 被修改成浮点数后返回 另外一种情况是, 结果不确定. ReadRealByName 查找文件节点返回它的浮点形值 double cvreadrealbyname( const CvFileStorage* fs, const CvFileNode* map, const char* name, double default_value=0. ); fs 202

203 初始化文件存储器 map 设置父图表 如果为 NULL, 函数在所有的高层节点 ( 流 ) 中检索 name 设置节点名 default_value 如果 node 为 NULL, 返回一个值 函数 cvreadrealbyname 是 cvgetfilenodebyname 和 cvreadreal 的简单重叠 ReadString 从文件节点中得到字符串文本 const char* cvreadstring( const CvFileNode* node, const char* default_value=null ); node 初始化文件节点 default_value 如果 node 为 NULL, 返回一个值 函数 cvreadstring 从文件节点中返回字符串文本 如果文件节点为 NULL, default_value 被返回 另外如果文件节点有类型 CV_NODE_STR, 则 data.str.ptr 被返回 另外一种情况是, 结果不确定 ReadStringByName 查找文件节点返回它的字符串文本 const char* cvreadstringbyname( const CvFileStorage* fs, const CvFileNode* map, default_value=null ); const char* name, const char* fs 初始化文件存储器 map 设置母图表 如果为 NULL, 函数在所有的高层节点 ( 流 ) 中检索 name 设置节点名 default_value 如果文件节点为 NULL, 返回一个值 函数 cvreadstringbyname 是 cvgetfilenodebyname 和 cvreadstring 的简单重叠 Read 解释对象并返回指向它的指针 203

204 void* cvread( CvFileStorage* fs, CvFileNode* node, CvAttrList* attributes=null ); fs 初始化文件存储器 node 设置对象根节点 attributes 不被使用的参数. 函数 cvread 解释用户对象 ( 在文件存储器子树中建立新的对象 ) 并返回 对象被解释, 必须按原有的支持读方法的类型 ( 参考 CvTypeInfo). 用类型名决定对象, 并在文件中被解释 如果对象是动态结构, 它将在内存中创建传递给 cvopenfilestorage 或者使 NULL 指针被建立在临时性内存中 当 cvreleasefilestorage 被调用时释放内存 如果对象不是动态结构, 将在堆中被建立, 释放它的内存需要用专用函数或通用函数 cvrelease ReadByName 查找对象并解释 void* cvreadbyname( CvFileStorage* fs, const CvFileNode* map, const char* name, CvAttrList* attributes=null ); fs 初始化文件存储器 map 设置父节点 如果它为 NULL, 函数从高层节点中查找 name 设置节点名称 attributes 不被使用的参数. 函数 cvreadbyname 是由 cvgetfilenodebyname 和 cvread 叠合的. ReadRawData 读重数 void cvreadrawdata( const CvFileStorage* fs, const CvFileNode* src, void* dst, const char* dt ); fs 初始化文件存储器 204

205 src dst dt 设置文件节点 ( 有序的 ) 来读数 设置指向目的数组的指针 数组元素的说明 格式参考 cvwriterawdata 函数 cvreadrawdata 从有序的文件节点中读取标量元素 StartReadRawData 初始化文件节点读取器 void cvstartreadrawdata( const CvFileStorage* fs, const CvFileNode* src, CvSeqReader* reader ); fs src reader 初始化文件存储器 设置文件节点 ( 序列 ) 来读数 设置顺序读取指针 函数 cvstartreadrawdata 初始化序列读取器从文件节点中读取数据 初始化的首部通过传给 cvreadrawdataslice 使用 ReadRawDataSlice 初始化文件节点序列 void cvreadrawdataslice( const CvFileStorage* fs, CvSeqReader* reader, int count, void* dst, const char* dt ); fs reader count dst dt 文件存储器 设置序列读取器. 用 cvstartreadrawdata. 初始化 被读取元素的数量 指向目的数组的指针 数组元素的说明 格式参考 cvwriterawdata 函数 cvreadrawdataslice 从文件节点读一个或多个元素, 组成一个序列用于指定数组 读入元素的总数由其他数组的元素总和乘以每个数组元素数目 例如如果 dt='2if', 函数将 205

206 读是 total*3 数量的序列元素 对于任何数组, 可以使用 cvsetseqreaderpos 自由定位, 跳过某些位置或者重复读取 运行时类型信息和通用函数 CvTypeInfo 类型信息 typedef int (CV_CDECL *CvIsInstanceFunc)( const void* struct_ptr ); typedef void (CV_CDECL *CvReleaseFunc)( void** struct_dblptr ); typedef void* (CV_CDECL *CvReadFunc)( CvFileStorage* storage, CvFileNode* node ); typedef void (CV_CDECL *CvWriteFunc)( CvFileStorage* storage, const char* name, const void* struct_ptr, CvAttrList attributes ); typedef void* (CV_CDECL *CvCloneFunc)( const void* struct_ptr ); typedef struct CvTypeInfo int flags; /* 不常用 */ int header_size; /* (CvTypeInfo) 的大小 sizeof(cvtypeinfo) */ struct CvTypeInfo* prev; /* 在列表中已定义过的类型 */ struct CvTypeInfo* next; /* 在列表中下一个已定义过的类型 */ const char* type_name; /* 定义类型名, 并写入文件存储器 */ /* methods */ CvIsInstanceFunc is_instance; /* 选择被传递的对象是否属于的类型 */ CvReleaseFunc release; /* 释放对象的内存空间 */ CvReadFunc read; /* 从文件存储器中读对象 */ CvWriteFunc write; /* 将对象写入文件存储器 */ CvCloneFunc clone; /* 复制一个对象 */ } CvTypeInfo; 结构 CvTypeInfo 包含的信息包括标准的或用户自定义的类型 类型的实例可能有也可能没有包含指向相应的 CvTypeInfo 结构的指针 在已有的对象中查找类型的方法是使用 cvtypeof 函数 在从文件存储器中读对象的时候已有的类型信息可以通过类型名使用 cvfindtype 来查找 用户可以通过 cvregistertype 定义一个新的类型, 并将类型信息结构加到文件列表的开始端, 它可以从标准类型中建立专门的类型, 重载基本的方法 RegisterType 定义新类型 206

207 void cvregistertype( const CvTypeInfo* info ); info 类型信息结构 函数 cvregistertype 定义一个新类型, 可以通过信息来描述它 这个函数在内存创建了一个 copy, 所以在用完以后, 应该删除它 UnregisterType 删除定义的类型 void cvunregistertype( const char* type_name ); type_name 被删除的类型的名称 函数 cvunregistertype 通过指定的名称删除已定义的类型 如果不知道类型名, 可以用 cvtypeof 或者连续扫描类型列表, 从 cvfirsttype 开始, 然后调用 cvunregistertype(info->type_name) FirstType 返回类型列表的首位 CvTypeInfo* cvfirsttype( void ); 函数 cvfirsttype 返回类型列表中的第一个类型 可以利用 CvTypeInfo 的 prev next 来实现遍历 FindType 通过类型名查找类型 CvTypeInfo* cvfindtype( const char* type_name ); type_name 类型名函数 cvfindtype 通过类型名查找指定的类型 如果找不到返回值为 NULL TypeOf 返回对象的类型 CvTypeInfo* cvtypeof( const void* struct_ptr ); struct_ptr 定义对象指针 207

208 函数 cvtypeof 查找指定对象的类型 它反复扫描类型列表, 调用每一个类型信息结构中的函数和方法与对象做比较, 直到它们中的一个的返回值不为 0 或者所有的类型都被访问 Release 删除对象 void cvrelease( void** struct_ptr ); struct_ptr 定义指向对象的双指针 函数 cvrelease 查找指定对象的类型, 然后调用 release Clone 克隆一个对象 void* cvclone( const void* struct_ptr ); struct_ptr 定义被克隆的对象函数 cvclone 查找指定对象的类型, 然后调用 clone Save 存储对象到文件中 void cvsave( const char* filename, const void* struct_ptr, const char* name=null, const char* comment=null, CvAttrList attributes=cvattrlist()); filename 初始化文件名 struct_ptr 指定要存储的对象 name 可选择的对象名 如果为 NULL, 对象名将从 filename 中列出 comment 可选注释 加在文件的开始处 attributes 可选属性 传递给 cvwrite 函数 cvsave 存储对象到文件 它给 cvwrite 提供一个简单的接口 Load 208

209 从文件中打开对象 void* cvload( const char* filename, CvMemStorage* memstorage=null, const char* name=null, const char** real_name=null ); filename 初始化文件名 memstorage 动态结构的内存, 例如 CvSeq 或 CvGraph 不能作用于矩阵或图像 : name 可选对象名 如果为 NULL, 内存中的第一个高层对象被打开 real_name 可选输出参数 它包括已打开的对象的名称 ( 如果 name=null 时有效 ) 函数 cvload 从文件中打开对象 它给 cvread 提供一个简单的接口. 对象被打开之后, 文件存储器被关闭, 所有的临时缓冲区被删除 因而, 为了能打开一个动态结构, 如序列, 轮廓或图像, 你应该为该函数传递一个有效的目标存储器 6.Cxcore 其它混合函数 CheckArr 检查输入数组的每一个元素是否是合法值 int cvcheckarr( const CvArr* arr, int flags=0, double min_val=0, double max_val=0); #define cvcheckarray cvcheckarr arr 待检查数组 flags 操作标志, 0 或者下面值的组合 : CV_CHECK_RANGE - 如果设置这个标志, 函数检查数组的每一个值是否在范围 [minval,maxval) 以内 ; 否则, 它只检查每一个元素是否是 NaN 或者 ±Inf CV_CHECK_QUIET - 设置这个标志后, 如果一个元素是非法值的或者越界时, 函数不会产生错误 min_val 有效值范围的闭下边界 只有当 CV_CHECK_RANGE 被设置的时候它才有作用 max_val 有效值范围的开上边界 只有当 CV_CHECK_RANGE 被设置的时候它才有作用 函数 cvcheckarr 检查每一个数组元素是否是 NaN 或者 ±Inf 如果 CV_CHECK_RANGE 被设定, 它将检查每一个元素是否大于等于 minval 并且小于 maxval 如果检查成功函数返 209

210 回非零值, 例如, 所有元素都是合法的并且在范围内, 如果检查失败则返回 0 在后一种情况下如果 CV_CHECK_QUIET 标志没有被设定, 函数将报出运行错误 KMeans2 按照给定的类别数目对样本集合进行聚类 void cvkmeans2( const CvArr* samples, int cluster_count, CvArr* labels, CvTermCriteria termcrit ); samples 输入样本的浮点矩阵, 每个样本一行 cluster_count 所给定的聚类数目 labels 输出整数向量 : 每个样本对应的类别标识 termcrit 指定聚类的最大迭代次数和 / 或精度 ( 两次迭代引起的聚类中心的移动距离 ) 函数 cvkmeans2 执行 k-means 算法搜索 cluster_count 个类别的中心并对样本进行分类, 输出 labels(i) 为样本 i 的类别标识 例子. 用 k-means 对高斯分布的随机样本进行聚类 #include "cxcore.h" #include "highgui.h" int main( int argc, char** argv ) #define MAX_CLUSTERS 5 CvScalar color_tab[max_clusters]; IplImage* img = cvcreateimage( cvsize( 500, 500 ), 8, 3 ); CvRNG rng = cvrng(0xffffffff); color_tab[0] = CV_RGB(255,0,0); color_tab[1] = CV_RGB(0,255,0); color_tab[2] = CV_RGB(100,100,255); color_tab[3] = CV_RGB(255,0,255); color_tab[4] = CV_RGB(255,255,0); cvnamedwindow( "clusters", 1 ); for(;;) int k, cluster_count = cvrandint(&rng)%max_clusters + 1; int i, sample_count = cvrandint(&rng)% ; 210

211 CvMat* points = cvcreatemat( sample_count, 1, CV_32FC2 ); CvMat* clusters = cvcreatemat( sample_count, 1, CV_32SC1 ); /* generate random sample from multigaussian distribution */ for( k = 0; k < cluster_count; k++ ) CvPoint center; CvMat point_chunk; center.x = cvrandint(&rng)%img->width; center.y = cvrandint(&rng)%img->height; cvgetrows( points, &point_chunk, k*sample_count/cluster_count, k == cluster_count - 1? sample_count : (k+1)*sample_count/cluster_count ); cvrandarr( &rng, &point_chunk, CV_RAND_NORMAL, cvscalar(center.x,center.y,0,0), cvscalar(img->width/6, img->height/6,0,0) ); } /* shuffle samples */ for( i = 0; i < sample_count/2; i++ ) CvPoint2D32f* pt1 = (CvPoint2D32f*)points->data.fl + cvrandint(&rng)%sample_count; CvPoint2D32f* pt2 = (CvPoint2D32f*)points->data.fl + cvrandint(&rng)%sample_count; CvPoint2D32f temp; CV_SWAP( *pt1, *pt2, temp ); } cvkmeans2( points, cluster_count, clusters, cvtermcriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0 )); cvzero( img ); for( i = 0; i < sample_count; i++ ) CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i]; int cluster_idx = clusters->data.i[i]; cvcircle( img, cvpointfrom32f(pt), 2, color_tab[cluster_idx], CV_FILLED ); } cvreleasemat( &points ); cvreleasemat( &clusters ); 211

212 } } cvshowimage( "clusters", img ); int key = cvwaitkey(0); if( key == 27 ) // 'ESC' break; SeqPartition 拆分序列为等效的类 typedef int (CV_CDECL* CvCmpFunc)(const void* a, const void* b, void* userdata); int cvseqpartition( const CvSeq* seq, CvMemStorage* storage, CvSeq** labels, CvCmpFunc is_equal, void* userdata ); seq 划分序列 storage 存储序列的等效类的存储器, 如果为空, 函数用 seq->storage 存储输出标签 labels 输出参数 指向序列指针的指针, 这个序列存储以 0 为开始的输入序列元素的标签 is_equal 比较函数指针 如果两个特殊元素是来自同一个类, 那这个比较函数返回非零值, 否则返回 0 划分算法用比较函数的传递闭包得到等价类 userdata 直接传递给 is_equal 函数的指针 函数 cvseqpartition 执行二次方程算法为拆分集合为一个或者更多的等效类 函数返回等效类的数目 例子 : 拆分二维点集 #include "cxcore.h" #include "highgui.h" #include <stdio.h> CvSeq* point_seq = 0; IplImage* canvas = 0; CvScalar* colors = 0; int pos = 10; int is_equal( const void* _a, const void* _b, void* userdata ) CvPoint a = *(const CvPoint*)_a; 212

213 CvPoint b = *(const CvPoint*)_b; double threshold = *(double*)userdata; return (double)(a.x - b.x)*(a.x - b.x) + (double)(a.y - b.y)*(a.y - b.y) <= threshold; } void on_track( int pos ) CvSeq* labels = 0; double threshold = pos*pos; int i, class_count = cvseqpartition( point_seq, 0, &labels, is_equal, &threshold ); printf("%4d classes\n", class_count ); cvzero( canvas ); for( i = 0; i < labels->total; i++ ) CvPoint pt = *(CvPoint*)cvGetSeqElem( point_seq, i ); CvScalar color = colors[*(int*)cvgetseqelem( labels, i )]; cvcircle( canvas, pt, 1, color, -1 ); } } cvshowimage( "points", canvas ); int main( int argc, char** argv ) CvMemStorage* storage = cvcreatememstorage(0); point_seq = cvcreateseq( CV_32SC2, sizeof(cvseq), sizeof(cvpoint), storage ); CvRNG rng = cvrng(0xffffffff); int width = 500, height = 500; int i, count = 1000; canvas = cvcreateimage( cvsize(width,height), 8, 3 ); colors = (CvScalar*)cvAlloc( count*sizeof(colors[0]) ); for( i = 0; i < count; i++ ) CvPoint pt; int icolor; pt.x = cvrandint( &rng ) % width; pt.y = cvrandint( &rng ) % height; cvseqpush( point_seq, &pt ); icolor = cvrandint( &rng ) 0x ; 213

214 } colors[i] = CV_RGB(icolor & 255, (icolor >> 8)&255, (icolor >> 16)&255); } cvnamedwindow( "points", 1 ); cvcreatetrackbar( "threshold", "points", &pos, 50, on_track ); on_track(pos); cvwaitkey(0); return 0; 7.Cxcore 错误处理和系统函数 [ ] 错误处理 在 OpenCV 中错误处理和 IPL (Image Processing Library) 很相似 如果调用函数出现错误将并不直接返回错误代码, 而是用 CV_ERROR 宏调用 cverror 函数报错, 按次序地, 用 cvseterrstatus 函数设置错误状态, 然后调用标准的或者用户自定义的错误处理器 ( 它可以显示一个消息对话框, 导出错误日志等等, 参考函数 cvredirecterror, cvnuldevreport, cvstderrreport, cvguiboxreport) 每个程序的线程都有一个全局变量, 它包含了错误状态 ( 一个整数值 ) 这个状态可以被 cvgeterrstatus 函数检索到 有三个错误处理模式 ( 参考 cvseterrmode 和 cvgeterrmode): Leaf Parent Silent 错误处理器被调用以后程序被终止 这是缺省值 它在调试中是很有用的, 当错误发生的时候立即产生错误信息 然而对于产生式系统, 后面两种模式提供的更多控制能力可能会更有用 错误处理器被调用以后程序不会被终止 栈被清空 ( 它用 C++ 异常处理机制完成写 / 输出 --w/o) 当调用 CxCore 的函数 cvgeterrstatus 起作用以后用户可以检查错误代码 和 Parent 模式相似, 但是没有错误处理器被调用 事实上, Leaf 和 Parent 模式的语义被错误处理器执行, 上面的描述对 cvnuldevreport, cvstderrreport. cvguiboxreport 的行为有一些细微的差别, 一些自定义的错误处理器可能语义上会有很大的不同 错误处理宏报错, 检查错误等的宏 214

215 /* special macros for enclosing processing statements within a function and separating them from prologue (resource initialization) and epilogue (guaranteed resource release) */ #define BEGIN #define END goto exit; exit: ; } /* proceeds to "resource release" stage */ #define EXIT goto exit /* Declares locally 函数 name for CV_ERROR() use */ #define CV_FUNCNAME( Name ) \ static char cvfuncname[] = Name /* Raises an error within the current context */ #define CV_ERROR( Code, Msg ) \ \ cverror( (Code), cvfuncname, Msg, FILE, LINE ); \ EXIT; \ } /* Checks status after calling CXCORE function */ #define CV_CHECK() \ \ if( cvgeterrstatus() < 0 ) \ CV_ERROR( CV_StsBackTrace, "Inner function failed." ); \ } /* Provies shorthand for CXCORE function call and CV_CHECK() */ #define CV_CALL( Statement ) \ \ Statement; \ CV_CHECK(); \ } /* Checks some condition in both debug and release configurations */ #define CV_ASSERT( Condition ) \ \ if(!(condition) ) \ CV_ERROR( CV_StsInternal, "Assertion: " #Condition " failed" ); \ } /* these macros are similar to their CV_... counterparts, but they do not need exit label nor cvfuncname to be defined */ 215

216 #define OPENCV_ERROR(status,func_name,err_msg)... #define OPENCV_ERRCHK(func_name,err_msg)... #define OPENCV_ASSERT(condition,func_name,err_msg)... #define OPENCV_CALL(statement)... 取代上面的讨论, 这里有典型的 CXCORE 函数和这些函数使用的样例 错误处理宏的使用 #include "cxcore.h" #include <stdio.h> void cvresizedct( CvMat* input_array, CvMat* output_array ) CvMat* temp_array = 0; // declare pointer that should be released anyway. CV_FUNCNAME( "cvresizedct" ); // declare cvfuncname macro, BEGIN ; // start processing. There may be some declarations just after this // but they couldn't be accessed from the epilogue. if(!cv_is_mat(input_array)!cv_is_mat(output_array) ) // use CV_ERROR() to raise an error CV_ERROR( CV_StsBadArg, "input_array or output_array are not valid matrices" ); // some restrictions that are going to be removed later, may be checked with CV_ASSERT() CV_ASSERT( input_array->rows == 1 && output_array->rows == 1 ); // use CV_CALL for safe function call CV_CALL( temp_array = cvcreatemat( input_array->rows, MAX(input_array->cols,output_array->cols), input_array->type )); if( output_array->cols > input_array->cols ) CV_CALL( cvzero( temp_array )); temp_array->cols = input_array->cols; CV_CALL( cvdct( input_array, temp_array, CV_DXT_FORWARD )); temp_array->cols = output_array->cols; CV_CALL( cvdct( temp_array, output_array, CV_DXT_INVERSE )); 216

217 CV_CALL( cvscale( output_array, output_array, 1./sqrt((double)input_array->cols*output_array->cols), 0 )); END ; // finish processing. Epilogue follows after the macro. // release temp_array. If temp_array has not been allocated before an error occured, cvreleasemat // takes care of it and does nothing in this case. cvreleasemat( &temp_array ); } int main( int argc, char** argv ) CvMat* src = cvcreatemat( 1, 512, CV_32F ); #if 1 /* no errors */ CvMat* dst = cvcreatemat( 1, 256, CV_32F ); #else CvMat* dst = 0; /* test error processing mechanism */ #endif cvset( src, cvrealscalar(1.), 0 ); #if 0 /* change 0 to 1 to suppress error handler invocation */ cvseterrmode( CV_ErrModeSilent ); #endif cvresizedct( src, dst ); // if some error occurs, the message box will popup, or a message will be // written to log, or some user-defined processing will be done if( cvgeterrstatus() < 0 ) printf("some error occured" ); else printf("everything is OK" ); return 0; } [ ] GetErrStatus 返回当前错误状态 int cvgeterrstatus( void ); 217

218 函数 cvgeterrstatus 返回当前错误状态 - 这个状态是被最近调用的 cvseterrstatus 设置的 注意, 在 Leaf 模式下错误一旦发生程序立即被终止, 因此对于总是需要调用函数仍然获得控制的应用, 可以调用 cvseterrmode 函数将错误模式设置为 Parent 或 Silent [ ] SetErrStatus 设置错误状态 void cvseterrstatus( int status ); status 错误状态 函数 cvseterrstatus 设置错误状态为指定的值 大多数情况下, 该函数被用来重设错误状态 ( 设置为 CV_StsOk) 以从错误中恢复 在其他情况下调用 cverror 或 CV_ERROR 更自然一些 [ ] GetErrMode 返回当前错误模式 int cvgeterrmode( void ); 函数 cvgeterrmode 返回当前错误模式 - 这个值是被最近一次 cvseterrmode 函数调用所设定的 [ ] SetErrMode 设置当前错误模式 #define CV_ErrModeLeaf 0 #define CV_ErrModeParent 1 #define CV_ErrModeSilent 2 int cvseterrmode( int mode ); mode 错误模式 函数 cvseterrmode 设置指定的错误模式 关于不同的错误模式的讨论参考本节开始. [ ] Error 产生一个错误 218

219 int cverror( int status, const char* func_name, const char* err_msg, const char* file_name, int line ); status 错误状态 func_name 产生错误的函数名 err_msg 关于错误的额外诊断信息 file_name 产生错误的文件名 line 产生错误的行号函数 cverror 设置错误状态为指定的值 ( 通过 cvseterrstatus), 如果错误模式不是 Silent, 调用错误处理器 [ ] ErrorStr 返回错误状态编码的原文描述 const char* cverrorstr( int status ); status 错误状态 函数 cverrorstr 返回指定错误状态编码的原文描述 如果是未知的错误状态该函数返回空 (NULL) 指针 [ ] RedirectError 设置一个新的错误处理器 typedef int (CV_CDECL *CvErrorCallback)( int status, const char* func_name, userdata ); const char* err_msg, const char* file_name, int line, void* CvErrorCallback cvredirecterror( CvErrorCallback error_handler, prev_userdata=null ); void* userdata=null, void** error_handler 新的错误处理器 219

220 userdata 传给错误处理器的任意透明指针 prev_userdata 指向前面分配给用户数据的指针的指针函数 cvredirecterror 在标准错误处理器或者有确定借口的自定义错误处理器中选择一个新的错误处理器 错误处理器和 cverror 函数有相同的参数 如果错误处理器返回非零的值, 程序终止, 否则, 程序继续运行 错误处理器通过 cvgeterrmode 检查当前错误模式而作出决定 [ ] cvnuldevreport cvstderrreport cvguiboxreport 提供标准错误操作 int cvnuldevreport( int status, const char* func_name, const char* err_msg, const char* file_name, int line, void* userdata ); int cvstderrreport( int status, const char* func_name, const char* err_msg, const char* file_name, int line, void* userdata ); int cvguiboxreport( int status, const char* func_name, const char* err_msg, const char* file_name, int line, void* userdata ); status 错误状态 func_name 产生错误的函数名 err_msg 关于错误的额外诊断信息 file_name 产生错误的文件名 line 产生错误的行号 userdata 指向用户数据的指针, 被标准错误操作忽略 函数 cvnulldevreport, cvstderrreport, cvguiboxreport 提供标准错误操作 cvguiboxreport 是 Win32 系统缺省的错误处理器, cvstderrreport - 其他系统. cvguiboxreport 弹出错误描述的消息框并提供几个选择 下面是一个消息框的例子, 如果和例子中的错误描述相同, 它和上面的例子代码可能是兼容的 220

221 错误消息对话框 如果错误处理器是 cvstderrreport, 上面的消息将被打印到标准错误输出, 程序将要终止和继续依赖于当前错误模式 错误消息打印到标准错误输出 ( 在 Leaf 模式 ) OpenCV ERROR: Bad argument (input_array or output_array are not valid matrices) in function cvresizedct, D:\User\VP\Projects\avl_proba\a.cpp(75) Terminating the application... [ ] 系统函数 [ ] Alloc 分配内存缓冲区 void* cvalloc( size_t size ); size 以字节为单位的缓冲区大小 函数 cvalloc 分配字节缓冲区大小并返回分配的缓冲区的指针 如果错误处理函数产生了一个错误报告则返回一个空 (NULL) 指针 缺省地 cvalloc 调用 icvalloc 而 icvalloc 调用 malloc, 然而用 cvsetmemorymanager 调用用户自定义的内存分配和释放函数也是可能的 [ ] Free 释放内存缓冲区 void cvfree( void** ptr ); 221

222 buffer 指向被释放的缓冲区的双重指针 函数 cvfree 释放被 cvalloc 分配的缓冲区 在退出的时候它清除缓冲区指针, 这就是为什么要使用双重指针的原因 如果 *buffer 已经是空 (NULL), 函数什么也不做 [ ] GetTickCount Returns number of tics int64 cvgettickcount( void ); 函数 cvgettickcount 返回从依赖于平台的事件 ( 从启动开始 CPU 的 ticks 数目, 从 1970 年开始的微秒数目等等 ) 开始的 tics 的数目 该函数对于精确测量函数/ 用户代码的执行时间是很有用的 要转化 tics 的数目为时间单位, 使用函数 cvgettickfrequency [ ] GetTickFrequency 返回每个微秒的 tics 的数目 double cvgettickfrequency( void ); 函数 cvgettickfrequency 返回每个微秒的 tics 的数目 因此, cvgettickcount() 和 cvgettickfrequency() 将给出从依赖于平台的事件开始的 tics 的数目 [ ] RegisterModule Registers another module 注册另外的模块 typedef struct CvPluginFuncInfo void** func_addr; void* default_func_addr; const char* func_names; int search_modules; int loaded_from; } CvPluginFuncInfo; typedef struct CvModuleInfo struct CvModuleInfo* next; 222

223 const char* name; const char* version; CvPluginFuncInfo* func_tab; } CvModuleInfo; int cvregistermodule( const CvModuleInfo* module_info ); module_info 模块信息函数 cvregistermodule 添加模块到已注册模块列表中 模块被注册后, 用 cvgetmoduleinfo 函数可以检索到它的信息 注册模块可以通过 CXCORE 的支持利用优化插件 (IPP, MKL,...) CXCORE, CV (computer vision), CVAUX (auxilary computer vision) 和 HIGHGUI (visualization & image/video acquisition) 自身就是模块的例子 通常注册后共享库就被载入 参考 cxcore/src/cxswitcher.cpp and cv/src/cvswitcher.cpp 获取细节信息, 怎样注册的参考 cxcore/src/cxswitcher.cpp, cxcore/src/_cxipp.h 显示了 IPP 和 MKL 是怎样连接到模块的 [ ] GetModuleInfo 检索注册模块和插件的信息 void cvgetmoduleinfo( const char* module_name, const char** version, const char** loaded_addon_plugins ); module_name 模块名, 或者 NULL, 则代表所有的模块 version 输出参数, 模块的信息, 包括版本信息 loaded_addon_plugins 优化插件的名字和版本列表, 这里 CXCORE 可以被找到和载入函数 cvgetmoduleinfo 返回一个或者所有注册模块的信息 返回信息被存储到库当中, 因此, 用户不用释放或者修改返回的文本字符 [ ] UseOptimized 在优化 / 不优化两个模式之间切换 int cvuseoptimized( int on_off ); 223

224 on_off 优化 (<>0) 或者不优化 (0). 函数 cvuseoptimized 在两个模式之间切换, 这里只有纯 C 才从 cxcore, OpenCV 等执行 如果可用 IPP 和 MKL 函数也可使用 当 cvuseoptimized(0) 被调用, 所有的优化库都不被载入 该函数在调试模式下是很有用的, IPP&MKL 不工作, 在线跨速比较等 它返回载入的优化函数的数目 注意, 缺省地优化插件是被载入的, 因此在程序开始调用 cvuseoptimized(1) 是没有必要的 ( 事实上, 它只会增加启动时间 ) [ ] SetMemoryManager 分配自定义 / 缺省内存管理函数 typedef void* (CV_CDECL *CvAllocFunc)(size_t size, void* userdata); typedef int (CV_CDECL *CvFreeFunc)(void* pptr, void* userdata); void cvsetmemorymanager( CvAllocFunc alloc_func=null, CvFreeFunc free_func=null, void* userdata=null ); alloc_func 分配函数 ; 除了 userdata 可能用来确定上下文关系外, 接口和 malloc 相似 free_func 释放函数 ; 接口和 free 相似 userdata 透明的传给自定义函数的用户数据函数 cvsetmemorymanager 设置将被 cvalloc,cvfree 和高级函数 ( 例如. cvcreateimage) 调用的用户自定义内存管理函数 ( 代替 malloc 和 free) 注意, 当用 cvalloc 分配数据的时候该函数被调用 当然, 为了避免无限递归调用, 它不允许从自定义分配 / 释放函数调用 cvalloc 和 cvfree 如果 alloc_func 和 free_func 指针是 NULL, 恢复缺省的内存管理函数 [ ] SetIPLAllocators 切换图像 IPL 函数的分配 / 释放 typedef IplImage* (CV_STDCALL* Cv_iplCreateImageHeader) (int,int,int,char*,char*,int,int,int,int,int, IplROI*,IplImage*,void*,IplTileInfo*); typedef void (CV_STDCALL* Cv_iplAllocateImageData)(IplImage*,int,int); typedef void (CV_STDCALL* Cv_iplDeallocate)(IplImage*,int); 224

225 typedef IplROI* (CV_STDCALL* Cv_iplCreateROI)(int,int,int,int,int); typedef IplImage* (CV_STDCALL* Cv_iplCloneImage)(const IplImage*); void cvsetiplallocators( Cv_iplCreateImageHeader create_header, Cv_iplAllocateImageData allocate_data, Cv_iplDeallocate deallocate, Cv_iplCreateROI create_roi, Cv_iplCloneImage clone_image ); #define CV_TURN_ON_IPL_COMPATIBILITY() \ cvsetiplallocators( iplcreateimageheader, iplallocateimage, \ ipldeallocate, iplcreateroi, iplcloneimage ) create_header 指向 iplcreateimageheader 的指针 allocate_data 指向 iplallocateimage 的指针 deallocate 指向 ipldeallocate 的指针 create_roi 指向 iplcreateroi 的指针 clone_image 指向 iplcloneimage 的指针函数 cvsetiplallocators 使用 CXCORE 来进行图像 IPL 函数的分配 / 释放操作 为了方便, 这里提供了环绕宏 CV_TURN_ON_IPL_COMPATIBILITY 当 IPL 和 CXCORE/OpenCV 同时使用以及调用 iplcreateimageheader 等情况该函数很有用 如果 IPL 仅仅是被调用来进行数据处理, 该函数就必要了, 因为所有的分配 / 释放都由 CXCORE 来完成, 或者所有的分配 / 释放都由 IPL 和一些 OpenCV 函数来处理数据 225

226 机器学习中文参考手册 1. 简介 : 通用类和函数 机器学习库 (MLL) 是一些用于分类 回归和数据聚类的类和函数 大部分分类和回归算法是用 C++ 类来实现 尽管这些算法有一些不同的特性 ( 像处理 missing measurements 的能力, 或者 categorical input variables 等 ), 这些类之间有一些相同之处 这些相同之处在类 CvStatModel 中被定义, 其他 ML 类都是从这个类中继承 1.1 CvStatModel ML 库中的统计模型基类 class CvStatModel public: /* CvStatModel(); */ /* CvStatModel( const CvMat* train_data... ); */ virtual ~CvStatModel(); virtual void clear()=0; /* virtual bool train( const CvMat* train_data, [int tflag,]..., const CvMat* responses,..., [const CvMat* var_idx,]..., [const CvMat* sample_idx,]... [const CvMat* var_type,]..., [const CvMat* missing_mask,] <misc_training_alg_params>... )=0; */ /* virtual float predict( const CvMat* sample... ) const=0; */ virtual void save( const char* filename, const char* name=0 )=0; virtual void load( const char* filename, const char* name=0 )=0; }; virtual void write( CvFileStorage* storage, const char* name )=0; virtual void read( CvFileStorage* storage, CvFileNode* node )=0; 226

227 在上面的声明中, 一些函数被注释掉 实际上, 一些函数没有一个单一的 API( 缺省的构造函数除外 ), 然而, 在本节后面描述的语法和定义方面有一些相似之处, 好像他们是基类的一部分一样 注意 :opencv 1.0 版本对 CvStatModel 类做了修改, 类的声明如下 class CV_EXPORTS CvStatModel public: CvStatModel(); virtual ~CvStatModel(); virtual void clear(); virtual void save( const char* filename, const char* name=0 ); virtual void load( const char* filename, const char* name=0 ); virtual void write( CvFileStorage* storage, const char* name ); virtual void read( CvFileStorage* storage, CvFileNode* node ); protected: const char* default_model_name; }; 1.2 CvStatModel::CvStatModel 缺省构造函数 CvStatModel::CvStatModel(); ML 中的每个统计模型都有一个无参数构造函数 这个构造函数在 " 两步法 " 构造时非常有用, 先调用这个缺省构造函数, 紧接着调用 tranin() 或者 load() 函数.(This constructor is useful for 2-stage model construction, when the default constructor is followed by train() or load().) 1.3 CvStatModel::CvStatModel(...) 训练构造函数 CvStatModel::CvStatModel( const CvMat* train_data... ); */ 大多数 ML 类都提供一个单步创建 + 训练的构造函数 此构造函数等价于缺省构造函数, 加上一个紧接的 train() 方法调用, 所传入的参数即为调用的参数 1.4 CvStatModel:: ~CvStatModel 227

228 虚拟析构函数 (Virtual destructor) CvStatModel::~CvStatModel(); 基类析构被声明为虚方法, 因此你可以安全地写出下面的代码 : CvStatModel* model; if( use_svm ) model = new CvSVM(... /* SVM params */); else model = new CvDTree(... /* Decision tree params */);... delete model; 一般, 每个继承类的析构器不用做任何操作, 但是如果调用了重载的 clear() 方法, 将释放全部内存资源 1.5 CvStatModel::clear 释放内存, 重置模型状态 void CvStatModel::clear(); clear 方法和析构函数发生的行为相似, 比如 :clear 方法释放类成员所占用的内存空间 然而, 和析构函数不同的是,clear 方法不析构对象自身, 也即调用 clear 方法后, 对象本身在将来仍然可以使用 一般情况下, 析构器 load 方法 read 方法 派生类 train 成员调用 clear 方法释放内存空间, 甚至是用户也可以进行明确的调用 1.6 CvStatModel::save 将模型保存到文件 void CvStatModel::save( const char* filename, const char* name=0 ); save 方法将整个模型状态以指定名称或默认名称 ( 取决于特定的类 ) 保存到指定的 XML 或 YAML 文件中 该方法使用的是 cxcore 中的数据保存功能 1.7 CvStatModel::load 从文件中装载模型 void CvStatModel::load( const char* filename, const char* name=0 ); 228

229 load 方法从指定的 XML 或 YAML 文件中装载指定名称 ( 或默认的与模型相关名称 ) 的整个模型状态 之前的模型状态将被 clear() 清零 请注意, 这个方法是虚的, 因此任何模型都可以用这个虚方法来加载 然而, 不像 OpenCV 中的 C 类型可以用通用函数 cvload() 来加载, 这里模型类型无论如何都要是已知的, 因为一个空模型作为恰当类的一种, 必须被预先建构 这个限制将会在未来的 ML 版本中移除 1.8 CvStatModel::write 将模型写入文件存储 void CvStatModel::write( CvFileStorage* storage, const char* name ); write 方法将整个模型状态用指定的或默认的名称 ( 取决于特定的类 ) 写到文件存储中去 这个方法被 save() 调用 1.9 CvStatModel::read 从文件存储中读出模型 void CvStatMode::read( CvFileStorage* storage, CvFileNode* node ); read 方法从文件存储中的指定节点中读取整个模型状态 这个节点必须由用户来定位, 如使用 cvgetfilenodebyname() 函数 这个方法被 load() 调用 之前的模型状态被 clear() 清零 1.10 CvStatModel::train 训练模型 bool CvStatMode::train( const CvMat* train_data, [int tflag,]..., const CvMat* responses,..., [const CvMat* var_idx,]..., [const CvMat* sample_idx,]... [const CvMat* var_type,]..., [const CvMat* missing_mask,] <misc_training_alg_params>... ); 这个函数利用输入的特征向量和对应的响应值 (responses) 来训练统计模型 特征向量和其对应的响应值都是用矩阵来表示 缺省情况下, 特征向量都以行向量被保存在 train_data 中, 也就是所有的特征向量元素都是连续存储 不过, 一些算法可以处理转置表示, 即特征向量用列向量来表示, 所有特征向量的相同位置的元素连续存储 如果两种排布方式都支持, 这个函数的参数 tflag 可以使用下面的取值 : tflag=cv_row_sample 表示特征向量以行向量存储 ; tflag=cv_col_sample 229

230 表示特征向量以列向量存储 ; 训练数据必须是 32fC1(32 位的浮点数, 单通道 ) 格式响应值通常是以向量方式存储 ( 一个行, 或者一个列向量 ), 存储格式为 32sC1( 仅在分类问题中 ) 或者 32fC1 格式, 每个输入特征向量对应一个值 ( 虽然一些算法, 比如某几种神经网络, 响应值为向量 ) 对于分类问题, 响应值是离散的类别标签 ; 对于回归问题, 响应值是被估计函数的输出值 一些算法只能处理分类问题, 一些只能处理回归问题, 另一些算法这两类问题都能处理 ML 中的很多模型也可以仅仅使用选择特征的子集, 或者 ( 并且 ) 使用选择样本的子集来训练 为了让用户易于使用,train 函数通常包含 var_idx 和 sample_idx 参数 var_idx 指定感兴趣的特征,sample_idx 指定感兴趣的样本 这两个向量可以是整数 (32sC1) 向量, 例如以 0 为开始的索引, 或者 8 位 (8uC1) 的使用的特征或者样本的掩码 用户也可以传入 NULL 指针, 用来表示训练中使用所有变量 / 样本 除此之外, 一些算法支持数据缺失情况, 也就是某个训练样本的某个特征值未知 ( 例如, 他们忘记了在周一测量病人 A 的温度 ) 参数 missing_mask, 一个 8 位的同 train_data 同样大小的矩阵掩码, 用来指示缺失的数据 ( 掩码中的非零值 ) 通常来说, 在执行训练操作前, 可以用 clear() 函数清除掉早先训练的模型状态 然而, 一些函数可以选择用新的数据更新模型, 而不是将模型重置, 一切从头再来 1.11 CvStatModel::predict 预测样本的 response float CvStatMode::predict( const CvMat* sample[, <prediction_params>] ) const; 这个函数用来预测一个新样本的响应值 (response) 在分类问题中, 这个函数返回类别编号 ; 在回归问题中, 返回函数值 输入的样本必须与传给 train_data 的训练样本同样大小 如果训练中使用了 var_idx 参数, 一定记住在 predict 函数中使用跟训练特征一致的特征 后缀 const 是说预测不会影响模型的内部状态, 所以这个函数可以很安全地从不同的线程调用 2.Normal Bayes 分类器 这个简单的分类器模型是建立在每一个类别的特征向量服从正态分布的基础上的 ( 尽管, 不必是独立的 ), 因此, 整个分布函数被假设为一个高斯分布, 每一类别一组系数 当给定了训练数据, 算法将会估计每一个类别的向量均值和方差矩阵, 然后根据这些进行预测 [Fukunaga90] K. Fukunaga. Introduction to Statistical Pattern Recognition. second ed., New York: Academic Press, 注 :OpenCV 1.0rc1(0.9.9) 版本的贝叶斯分类器有个小 bug, 训练数据时候会提示错误 230

231 OpenCV ERROR: Formats of input arguments do not match () in function cvsvd, cxsvd.cpp(1243) 修改方法为将文件 ml/src/mlnbayes.cpp 中的 193 行 : CV_CALL( cov = cvcreatemat( _var_count, _var_count, CV_32FC1 )); 改为 CV_CALL( cov = cvcreatemat( _var_count, _var_count, CV_64FC1 )); 此问题在 OpenCV 中已经得到修正 2.1 CvNormalBayesClassifier 对正态分布的数据的贝叶斯分类器 class CvNormalBayesClassifier : public CvStatModel public: CvNormalBayesClassifier(); virtual ~CvNormalBayesClassifier(); CvNormalBayesClassifier( const CvMat* _train_data, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0 ); virtual bool train( const CvMat* _train_data, const CvMat* _responses, const CvMat* _var_idx = 0, const CvMat* _sample_idx=0, bool update=false ); virtual float predict( const CvMat* _samples, CvMat* results=0 ) const; virtual void clear(); virtual void save( const char* filename, const char* name=0 ); virtual void load( const char* filename, const char* name=0 ); virtual void write( CvFileStorage* storage, const char* name ); virtual void read( CvFileStorage* storage, CvFileNode* node ); protected:... }; 2.2 CvNormalBayesClassifier::train 训练这个模型 231

232 bool CvNormalBayesClassifier::train( const CvMat* _train_data, const CvMat* _responses, const CvMat* _var_idx = 0, const CvMat* _sample_idx=0, bool update=false ); 这个函数训练正态贝叶斯分类器 并且遵循通常训练 函数 的以下一些限制 : 只支持 CV_ROW_SAMPLE 类型的数据, 输入的变量全部应该是有序的, 输出的变量是一个分类结果 ( 例如,_responses 中的元素必须是整数, 因此向量的类型有可能是 32fC1 类型的 ), 不支持 missing, measurements 另外, 有一个 update 标志, 标志着模型是否使用新数据升级 2.3 CvNormalBayesClassifier::predict 对未知的样本或或本集进行预测 float CvNormalBayesClassifier::predict( const CvMat* samples, CvMat* results=0 ) const; 这个函数估计输入向量的最有可能的类别 输入向量 ( 一个或多个 ) 被储存在矩阵的每一行中 对于多个输入向量, 则输出会是一个向量结果 对于单一的输入, 函数本身的返回值就是预测结果 3.K 近邻算法 这个算法首先贮藏所有的训练样本, 然后通过分析 ( 包括选举, 计算加权和等方式 ) 一个新样本周围 K 个最近邻以给出该样本的相应值 这种方法有时候被称作 基于样本的学习, 即为了预测, 我们对于给定的输入搜索最近的已知其相应的特征向量 3.1 CvKNearest K 近邻类 class CvKNearest : public CvStatModel // 继承自 ML 库中的统计模型基类 public: CvKNearest(); virtual ~CvKNearest(); // 虚函数定义 CvKNearest( const CvMat* _train_data, const CvMat* _responses, const CvMat* _sample_idx=0, bool _is_regression=false, int max_k=32 ); 232

233 virtual bool train( const CvMat* _train_data, const CvMat* _responses, const CvMat* _sample_idx=0, bool is_regression=false, int _max_k=32, bool _update_base=false ); virtual float find_nearest( const CvMat* _samples, int k, CvMat* results, const float** neighbors=0, CvMat* neighbor_responses=0, CvMat* dist=0 ) const; virtual void clear(); int get_max_k() const; int get_var_count() const; int get_sample_count() const; bool is_regression() const; protected:... }; 3.2 CvKNearest::train 训练 KNN 模型 bool CvKNearest::train( const CvMat* _train_data, const CvMat* _responses, const CvMat* _sample_idx=0, bool is_regression=false, int _max_k=32, bool _update_base=false ); 这个类的方法训练 K 近邻模型 它遵循一个一般训练方法约定的限制 : 只支持 CV_ROW_SAMPLE 数据格式, 输入向量必须都是有序的, 而输出可以是无序的 ( 当 is_regression=false), 可以是有序的 (is_regression=true) 并且变量子集和省略度量是不被支持的 参数 _max_k 指定了最大邻居的个数, 它将被传给方法 find_nearest 参数 _update_base 指定模型是由原来的数据训练 (_update_base=false), 还是被新训练数据更新后再训练 (_update_base=true) 在后一种情况下_max_k 不能大于原值, 否则它会被忽略. 3.3 CvKNearest::find_nearest 寻找输入向量的最近邻 float CvKNearest::find_nearest( const CvMat* _samples, int k, CvMat* results=0, const float** neighbors=0, CvMat* neighbor_responses=0, CvMat* dist=0 ) const; 对每个输入向量 ( 表示为 matrix_sample 的每一行 ), 该方法找到 k(k get_max_k() ) 个最近邻 在回归中, 预测结果将是指定向量的近邻的响应的均值 在分类中, 类别将由投票决定 233

234 对传统分类和回归预测来说, 该方法可以有选择的返回近邻向量本身的指针 (neighbors, array of k*_samples->rows pointers), 它们相对应的输出值 (neighbor_responses, a vector of k*_samples->rows elements), 和输入向量与近邻之间的距离 (dist, also a vector of k*_samples->rows elements) 对每个输入向量来说, 近邻将按照它们到该向量的距离排序 对单个输入向量, 所有的输出矩阵是可选的, 而且预测值将由该方法返回 In case of a single input vector all the output matrices are optional and the predicted value is returned by the method. 3.4 例程 : 使用 knn 进行 2 维样本集的分类, 样本集的分布为混合高斯分布 #include "ml.h" #include "highgui.h" int main( int argc, char** argv ) const int K = 10; int i, j, k, accuracy; float response; int train_sample_count = 100; CvRNG rng_state = cvrng(-1); CvMat* traindata = cvcreatemat( train_sample_count, 2, CV_32FC1 ); CvMat* trainclasses = cvcreatemat( train_sample_count, 1, CV_32FC1 ); IplImage* img = cvcreateimage( cvsize( 500, 500 ), 8, 3 ); float _sample[2]; CvMat sample = cvmat( 1, 2, CV_32FC1, _sample ); cvzero( img ); CvMat traindata1, traindata2, trainclasses1, trainclasses2; // form the training samples cvgetrows( traindata, &traindata1, 0, train_sample_count/2 ); cvrandarr( &rng_state, &traindata1, CV_RAND_NORMAL, cvscalar(200,200), cvscalar(50,50) ); cvgetrows( traindata, &traindata2, train_sample_count/2, train_sample_count ); cvrandarr( &rng_state, &traindata2, CV_RAND_NORMAL, cvscalar(300,300), cvscalar(50,50) ); cvgetrows( trainclasses, &trainclasses1, 0, train_sample_count/2 ); cvset( &trainclasses1, cvscalar(1) ); 234

235 cvgetrows( trainclasses, &trainclasses2, train_sample_count/2, train_sample_count ); cvset( &trainclasses2, cvscalar(2) ); // learn classifier CvKNearest knn( traindata, trainclasses, 0, false, K ); CvMat* nearests = cvcreatemat( 1, K, CV_32FC1); for( i = 0; i < img->height; i++ ) for( j = 0; j < img->width; j++ ) sample.data.fl[0] = (float)j; sample.data.fl[1] = (float)i; // estimates the response and get the neighbors' labels response = knn.find_nearest(&sample,k,0,0,nearests,0); } } // compute the number of neighbors representing the majority for( k = 0, accuracy = 0; k < K; k++ ) if( nearests->data.fl[k] == response) accuracy++; } // highlight the pixel depending on the accuracy (or confidence) cvset2d( img, i, j, response == 1? (accuracy > 5? CV_RGB(180,0,0) : CV_RGB(180,120,0)) : (accuracy > 5? CV_RGB(0,180,0) : CV_RGB(120,120,0)) ); // display the original training samples for( i = 0; i < train_sample_count/2; i++ ) CvPoint pt; pt.x = cvround(traindata1.data.fl[i*2]); pt.y = cvround(traindata1.data.fl[i*2+1]); cvcircle( img, pt, 2, CV_RGB(255,0,0), CV_FILLED ); pt.x = cvround(traindata2.data.fl[i*2]); pt.y = cvround(traindata2.data.fl[i*2+1]); cvcircle( img, pt, 2, CV_RGB(0,255,0), CV_FILLED ); } cvnamedwindow( "classifier result", 1 ); 235

236 cvshowimage( "classifier result", img ); cvwaitkey(0); } cvreleasemat( &trainclasses ); cvreleasemat( &traindata ); return 0; 4. 支持向量机部分 支持向量机 (SVM), 起初由 vapnik 提出时, 是作为寻求最优 ( 在一定程度上 ) 二分类器的一种技术 后来它又被拓展到回归和聚类应用 SVM 是一种基于核函数的方法, 它通过某些核函数把特征向量映射到高维空间, 然后建立一个线性判别函数 ( 或者说是一个高维空间中的能够区分训练数据的最优超平面, 参考异或那个经典例子 ) 假如 SVM 没有明确定义核函数, 高维空间中任意两点距离就需要定义 解是最优的在某种意义上是两类中距离分割面最近的特征向量和分割面的距离最大化 离分割面最近的特征向量被称为 支持向量, 意即其它向量不影响分割面 ( 决策函数 ) 有很多关于 SVM 的参考文献, 这是两篇较好的入门文献 Burges98 C. Burges. "A tutorial on support vector machines for pattern recognition", Knowledge Discovery and Data Mining 2(2), (available online at [1]). LIBSVM - A Library for Support Vector Machines. By Chih-Chung Chang and Chih-Jen Lin ([2]) 4.1 CvSVM 支持矢量机 class CvSVM : public CvStatModel // 继承自基类 CvStatModel public: // SVM type enum C_SVC=100, NU_SVC=101, ONE_CLASS=102, EPS_SVR=103, NU_SVR=104 };//SVC 是 SVM 分类器,SVR 是 SVM 回归 // SVM kernel type enum LINEAR=0, POLY=1, RBF=2, SIGMOID=3 }; // 提供四种核函数, 分别是线性, 多项式, 径向基,sigmoid 型函数 CvSVM(); virtual ~CvSVM(); 236

237 CvSVM( const CvMat* _train_data, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, CvSVMParams _params=cvsvmparams() ); virtual bool train( const CvMat* _train_data, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, CvSVMParams _params=cvsvmparams() ); virtual float predict( const CvMat* _sample ) const; virtual int get_support_vector_count() const; virtual const float* get_support_vector(int i) const; virtual void clear(); virtual void save( const char* filename, const char* name=0 ); virtual void load( const char* filename, const char* name=0 ); virtual void write( CvFileStorage* storage, const char* name ); virtual void read( CvFileStorage* storage, CvFileNode* node ); int get_var_count() const return var_idx? var_idx->cols : var_all; } protected:... }; 4.2 CvSVMParams SVM 训练参数 struct struct CvSVMParams CvSVMParams(); CvSVMParams( int _svm_type, int _kernel_type, double _degree, double _gamma, double _coef0, double _C, double _nu, double _p, CvMat* _class_weights, CvTermCriteria _term_crit ); int int double double double svm_type; kernel_type; degree; // for poly gamma; // for poly/rbf/sigmoid coef0; // for poly/sigmoid double double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR and CV_SVM_NU_SVR nu; // for CV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR 237

238 }; double p; // for CV_SVM_EPS_SVR CvMat* class_weights; // for CV_SVM_C_SVC CvTermCriteria term_crit; // termination criteria svm_type,svm 的类型 : CvSVM::C_SVC - n(n>=2) 分类器, 允许用异常值惩罚因子 C 进行不完全分类 CvSVM::NU_SVC - n 类似然不完全分类的分类器 参数 nu 取代了 c, 其值在区间 0, 1 中,nu 越大, 决策边界越平滑 CvSVM::ONE_CLASS - 单分类器, 所有的训练数据提取自同一个类里, 然后 SVM 建立了一个分界线以分割该类在特征空间中所占区域和其它类在特征空间中所占区域 CvSVM::EPS_SVR - 回归 训练集中的特征向量和拟合出来的超平面的距离需要小于 p 异常值惩罚因子 C 被采用 CvSVM::NU_SVR - 回归 ;nu 代替了 p kernel_type// 核类型 : CvSVM::LINEAR - 没有任何向映射至高维空间, 线性区分 ( 或回归 ) 在原始特征空间中被完成, 这是最快的选择 d(x,y) = x y == (x,y) CvSVM::POLY - 多项式核 : d(x,y) = (gamma*(x y)+coef0)degree CvSVM::RBF - 径向基, 对于大多数情况都是一个较好的选择 :d(x,y) = exp(-gamma* x-y 2) CvSVM::SIGMOID - sigmoid 函数被用作核函数 : d(x,y) = tanh(gamma*(x y)+coef0) degree, gamma, coef0: 都是核函数的参数, 具体的参见上面的核函数的方程 C, nu, p: 在一般的 SVM 优化求解时的参数 class_weights: 可选权重, 赋给指定的类别 一般乘以 C 以后去影响不同类别的错误分类惩罚项 权重越大, 某一类别的误分类数据的惩罚项就越大 term_crit:svm 的迭代训练过程的中止 ( 解决了部分受约束二次最优问题 ) 该结构需要初始化, 并传递给 CvSVM 的训练函数 4.3 CvSVM::train 训练 SVM bool CvSVM::train( const CvMat* _train_data, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, CvSVMParams _params=cvsvmparams() ); 所有的参数都被集成在 CvSVMParams 这个结构中 238

239 4.4 CvSVM::get_support_vector* 得到支持矢量和特殊矢量的数 int CvSVM::get_support_vector_count() const; const float* CvSVM::get_support_vector(int i) const; 这个方法可以被用来得到支持矢量的集合 4.5 补充 : 在 WindowsXP+OpenCVRC1 平台下整合 OpenCV 与 libsvm 虽然从 RC1 版开始 opencv 开始增设 ML 类, 提供对常见的分类器和回归算法的支持 但是尚存在一些问题, 比如说例子少 ( 官方许诺说很快会提供一批新例子, 见 CVS 版 ) 单说 SVM 这种算法, 它自己提供了一套比较完备的函数, 但是并不见得优于老牌的 libsvm( 它也应该参考过 libsvm, 至于是否效率优于 libsvm, 我并没有作过测试, 官方也没有什么说法, 但是 libsvm 持续开源更新, 是公认的现存的开源 SVM 库中最易上手, 性能最好的库 ) 所以在你的程序里整合 opencv 和 libsvm 还是一种比较好的解决方案 在 VC 中整合有些小地方需要注意, 这篇文档主要是提供把图象作为 SVM 输入时程序遇到的这些小问题的解决方案 希望大家遇到问题时, 多去读 libsvm 的源码, 它本身也是开源的,C 代码写得也很优秀, 数据结构定义得也比较好 首先是 SVM 的训练, 这部分我并没有整合到 VC 里, 直接使用它提供的 python 程序, 虽然网格搜索这种简易搜索也可以自己写, 但是识别时只需要训练生成的 SVMmodel 文件即可, 所以可以和主程序分离开 至于 python 在 windows 下的使用, 还是要设置一下的, 首先要在系统环境变量 path 里把 python 的路径设进去,libsvm 画训练等高线图还需要 gnuplot 的支持, 打开 python 源程序 (grid.py), 把 gnuplot_exe 设置成你机器里的安装路径, 这样才能正确的运行程序 然后就是改步长和搜索范围, 官方建议是先用大步长搜索, 搜到最优值后再用小步长在小范围内搜索 ( 我觉得这有可能会陷入局部最优, 不过近似出的结果还可以接受 ) 我用的 python 版本是 2.4,gnuplot 常用 libsvm 资料链接 官方站点, 有一些 tutorial 和测试数据 哈工大的机器学习论坛, 非常好 ( 外网似乎不能登录 ) 上交的一个研究生还写过 libsvm2.6 版的代码中文注释, 源链接找不着了, 大家自己搜搜吧, 写得很好, 上海交通大学模式分析与机器智能实验室 5. 决策树 239

240 本节所讨论的 ML 类 (s) 实现了 [Brieman84] 中描述的分类与回归树算法 类 CvDTree 可以表示一个单独使用的简单决策树, 也可以表示树集成分类器中的一个基础分类器 ( 参见 Boosting 和 Random Trees) 决策树是一个二叉树 ( 即树的非叶节点仅有两个子节点 ) 当每个叶节点用类别标识( 多个叶子可能有相同的标识 ) 时, 它可以表示分类树 ; 当每个叶节点被分配了一个常量 ( 所以回归函数是分段常量 ) 时, 决策树就成了回归树 用决策树进行预测 预测算法从根结点开始, 到达某个叶结点, 然后得到输入特征向量的响应 在每一个非叶子结点, 算法会根据变量值选择向左走或者向右走 ( 比如选择左子结点作为下一个观测结点 ), 该变量的索引值储存在被观测结点中 这个变量可以是数值的或者类型的 如果变量是数值的, 那么变量值就跟一个固定的阈值 ( 也储存在被观测结点中 ) 来比较, 如果该变量小于阈值, 那么算法就往左走, 否则就往右 ( 比如说, 如果重量小于 1kg, 那么算法流程就往左走, 否则就往右 ) 如果变量值是 categorical 的, 那么这个离散变量值会被测试是否属于某个特定的子集 ( 也储存在该结点中 ), 这个子集取自于一个该变量可以取到的有限集合 如果变量属于该子集, 那么算法流程就往左走, 否则就往右 ( 比如, 如果颜色是绿色的或者红色的, 就往左, 否则就往右 ) 也就是说, 在每个结点, 使用一对实体对象 (<variable_index>, <decision_rule (threshold/subset)>) 这叫做分裂点( 在变量 #<variable_index> 分裂 ) 如果到达了某一个叶子结点, 赋给该结点的值就作为预测算法的输出值 有时候, 输入向量的某些特征缺失 ( 比如说, 在黑暗中很难去确定对象的颜色 ), 而且预测算法可能在某一个结点上 ( 在上面提到结点用色彩划分的例子里 ) 反复运算 决策树用替代分裂点 (surrogate splits) 来避免这样的情况发生 这就是说, 除了最佳初始分裂点 (the best "primary" split) 以外, 每一个树结点可能都要被一个或多个几乎有一样的结果的变量分裂 Training Decision Trees 训练决策树 决策树是从根结点递归构造的 用所有的训练数据 ( 特征向量和对应的响应 ) 来在根结点处进行分裂 在每个结点处, 优化准则 ( 比如最优分裂 ) 是基于一些基本原则来确定的 ( 比如 ML 中的 纯度 purity 原则被用来进行分类, 方差之和用来进行回归 ) Then, if necessary, the surrogate splits are found that resemble at the most the results of the primary split on the training data; 所有的数据根据初始和替代分裂点来划分给左 右孩子结点 ( 就像在预测算法里做的一样 ) 然后算法回归的继续分裂左右孩子结点 在以下情况下算法可能会在某个结点停止 (i.e. stop splitting the node further): 树的深度达到了指定的最大值 在该结点训练样本的数目少于指定值, 比如, 没有统计意义上的集合来进一步进行结点分裂了 在该结点所有的样本属于同一类 ( 或者, 如果是回归的话, 变化已经非常小了 ) 跟随机选择相比, 能选择到的最好的分裂已经基本没有什么有意义的改进了 240

241 当树建好之后, 如果需要的话, 可能需要用交叉验证来进行剪枝 这就是说, 树的某些导致模型过拟合的分支将被剪掉 一般情况下, 这个过程只用于单决策树上, 因为 tree ensembles 通常会建立一些小的足够的树并且用他们自身的保护机制来防止过拟合 变量的重要性 Variable importance 决策树除了它的重要用途 预测以外, 还可以用在多变量分析上 构造好的决策树算法的一个关键特性就是它可能被用在计算每个变量的重要性 ( 相关决策力 ) 上 举个例子, 在一个垃圾邮件过滤器中, 用一个经常出现在邮件中的词汇集合来作为特征向量, 那么变量的重要率就可以用来决定最 垃圾邮件指示词, 这样就可以保证词汇集合的大小的合理性 每个变量的重要性的计算是在所有的在这个变量上的分裂进行的, 不管是初始的还是替代的 这样的话, 要准确计算变量重要性, 即使没有缺失数据, 替代分裂也必须包含在训练参数中 5.1 CvDTreeSplit Decision tree node split struct CvDTreeSplit int var_idx; int inversed; float quality; CvDTreeSplit* next; union int subset[2]; struct float c; int split_point; } ord; }; }; var_idx 分裂中所用到的变量的索引 inversed 当等于 1 的时候, 采用 inverse split rule( 比如, 左分支和右分支在表达式下被交换 ) quality 分裂值, 正数 用来选择最佳初始分裂点, 然后用来对替代分裂点进行选择和排序 构造好树之后, 还用来计算向量重要性 next 指向结点分裂表中的下一个分裂点 241

242 subset 二值集合, 用在在类别向量的分裂上 规则如下 : 如果 var_value 在 subset 里, 那么 next_node<-left, 否则 next_node<-right c 用在数值变量的分裂上的阈值 规则如下 : 如果 var_value<c, 那么 next_node<-left, 否则 next_node<-right split_point 用在训练算法内部 5.2 CvDTreeNode Decision tree node struct CvDTreeNode int class_idx; int Tn; double value; CvDTreeNode* parent; CvDTreeNode* left; CvDTreeNode* right; CvDTreeSplit* split; int sample_count; int depth;... }; value 赋给结点的值 可以是一个类别标签, 也可以是估计函数值 class_idx 赋给结点的归一化的类别索引 ( 从 0 到 class_count-1 ), 用在分类树和树集成中 Tn 在有序排列的树中的树索引 用在剪枝过程中和之后 在整棵树中根结点有最大的 Tn 值, 子结点的 Tn 值小于或等于父结点,Tn CvDTree::pruned_tree_idx 的结点在预测阶段不予考虑 ( 对应的分枝也被剪掉 ), 即使它们还没有在物理上被从树上移除 parent, left, right 指向父结点 左右孩子结点的指针 split 指向第一个分裂点 ( 初始分裂点 ) 的指针 sample_count 在训练阶段所用到的样本数目 用来解决复杂的问题 当初始分裂点所用到的变量缺失时, 而且其他替代分裂点所用到的变量也缺失时, 如果 242

243 depth left->sample_count>right->sample_count, 那么样本直接进入左边的子结点, 否则往右 结点深度, 根结点的深度是 0, 子结点的深度是父结点的深度加 1 CvDTreeNode 的其他数据在训练阶段使用 5.3 CvDTreeParams Decision tree training parameters struct CvDTreeParams int max_categories; int max_depth; int min_sample_count; int cv_folds; bool use_surrogates; bool use_1se_rule; bool truncate_pruned_tree; float regression_accuracy; const float* priors; CvDTreeParams() : max_categories(10), max_depth(int_max), min_sample_count(10), cv_folds(10), use_surrogates(true), use_1se_rule(true), truncate_pruned_tree(true), regression_accuracy(0.01f), priors(0) } CvDTreeParams( int _max_depth, int _min_sample_count, float _regression_accuracy, bool _use_surrogates, int _max_categories, int _cv_folds, bool _use_1se_rule, bool _truncate_pruned_tree, const float* _priors ); }; max_depth 该参数指定树的最大可能深度 当某结点的深度小于 max_depth 时, 训练算法将继续试图分裂该结点 如果满足了其他的终止准则, 那么实际深度可能比 max_depth 小 ( 参见本部门的训练过程 ), 而且可能会被剪枝 min_sample_count 如果跟某结点相关的样本数量小于该参数值那么该结点就不被分裂 regression_accuracy 另一个终止准则 只用于回归树 一旦估计结点值与训练样本的响应的差小于该参数值, 该结点就不再分裂 use_surrogates 243

244 如果该参数为真, 替代结点就被建立 替代结点用在解决缺失数据和变量重要性估计 max_categories 在训练过程试图进行分裂时, 如果一个离散变量比 max_categories 大, 最佳精度子集的估计就需要花很长时间 ( 比如算法是指数型的 ) 作为替代, 许多决策树引擎 ( 包括 ML) 试图通过把所有样本间聚成 max_categories 个类来找到最优子分裂点 ( 比如, 把一些类别聚到一起 ) 要注意到, 这个技术只用于 n(n>2) 类分类问题上 在回归问题和 2 类分类问题上, 不用聚类就可以有效地找到最优的分裂点, 在这些情况下就不用这个参数 cv_folds 如果该参数 >1, 就用 cv_folds 叠一交叉验证来进行剪枝 use_1se_rule 如果为真, 就用剪枝算法减去树顶端的一些部分 这会让树变得紧致, 而且对训练数据的噪声更有抵抗力一些, 但是会牺牲一些精度 truncate_pruned_tree 如果为真, 要被剪掉的结点 (with Tn CvDTree::pruned_tree_idx) 将被从树上移除 除非它们被保留, 而且 CvDTree::pruned_tree_idx( 比如设该值为 -1) 递减, 那么仍有可能从最初的没有被剪枝的树中获得结果 priors 类别先验概率的 array, 按类别标签值排序 该参数可以用在针对某个特定的类别而对决策树进行剪枝时 比如, 如果用户希望探测到某个比较少见的异常变化, 但训练可能包含了比异常多得多的正常情况, 那么很可能分类结果就是认为每一个情况都是正常的 为了避免这一点, 先验就必须被指定, 异常情况发生的概率需要人为的增加 ( 增加到 0.5 甚至更高 ), 这样误分类的异常情况的权重就会变大, 树也能够得到适当的调整 关于内存管理 :the field priors is a pointer to the array of floats 该列向量应该由用户指定分配, 并且在 CvDTreeParams 这个结构传递给 CvDTreeTrainData 或者 CvDTree 构造函数或者方法 ( 就像做了一个列向量的拷贝 ) 之后进行释放 该结构包含了所有的决策树训练所需的参数 一个缺省的构造函数可以用缺省值来初始化所有的参数, 构造一棵基本的分类树 任何参数都可以 overridden, 或者结构可以用构造函数的高级变量来进行完整的初始化 The structure 5.4 CvDTreeTrainData Decision tree training data and shared data for tree ensembles struct CvDTreeTrainData CvDTreeTrainData(); CvDTreeTrainData( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, 244

245 const CvMat* _missing_mask=0, const CvDTreeParams& _params=cvdtreeparams(), bool _shared=false, bool _add_labels=false ); virtual ~CvDTreeTrainData(); virtual void set_data( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, const CvDTreeParams& _params=cvdtreeparams(), bool _shared=false, bool _add_labels=false, bool _update_data=false ); virtual void get_vectors( const CvMat* _subsample_idx, float* values, uchar* missing, float* responses, bool get_class_idx=false ); virtual CvDTreeNode* subsample_data( const CvMat* _subsample_idx ); virtual void write_params( CvFileStorage* fs ); virtual void read_params( CvFileStorage* fs, CvFileNode* node ); // release all the data virtual void clear(); int get_num_classes() const; int get_var_type(int vi) const; int get_work_var_count() const; virtual int* get_class_labels( CvDTreeNode* n ); virtual float* get_ord_responses( CvDTreeNode* n ); virtual int* get_labels( CvDTreeNode* n ); virtual int* get_cat_var_data( CvDTreeNode* n, int vi ); virtual CvPair32s32f* get_ord_var_data( CvDTreeNode* n, int vi ); virtual int get_child_buf_idx( CvDTreeNode* n ); //////////////////////////////////// virtual bool set_params( const CvDTreeParams& params ); virtual CvDTreeNode* new_node( CvDTreeNode* parent, int count, int storage_idx, int offset ); virtual CvDTreeSplit* new_split_ord( int vi, float cmp_val, int split_point, int inversed, float quality ); 245

246 virtual CvDTreeSplit* new_split_cat( int vi, float quality ); virtual void free_node_data( CvDTreeNode* node ); virtual void free_train_data(); virtual void free_node( CvDTreeNode* node ); int sample_count, var_all, var_count, max_c_count; int ord_var_count, cat_var_count; bool have_labels, have_priors; bool is_classifier; int buf_count, buf_size; bool shared; CvMat* cat_count; CvMat* cat_ofs; CvMat* cat_map; CvMat* counts; CvMat* buf; CvMat* direction; CvMat* split_buf; CvMat* var_idx; CvMat* var_type; // i-th element = // k<0 - ordered // k>=0 - categorical, see k-th element of cat_* arrays CvMat* priors; CvDTreeParams params; CvMemStorage* tree_storage; CvMemStorage* temp_storage; CvDTreeNode* data_root; CvSet* node_heap; CvSet* split_heap; CvSet* cv_heap; CvSet* nv_heap; }; CvRNG rng; 这个结构体通常有效的用在存储单树和决策树集成中 通常包含 3 类信息 : 246

247 1. 训练参数,CvDTreeParams instance 2. 训练数据, 通常为了有效地找到最佳分裂点, 需要被预处理 对树集成来说, 被预处理后的数据要反复用在所有的树中 另外, 训练数据特征为树集成中的所有的树所共享, 并如下储存 : 变量类型, 类别数, 类别标签压缩图等 3. Buffers, 树结点 分裂点和其他树结构元素的内存储存 有两种使用该结构的方式 在一些简单的情况下 ( 比如, 单树, 或者从 ML 中得到的黑盒子树集成, 如随机树或者 boosting), 这就没有需要去注意甚至了解这个结构 只要去构建需要的统计模型, 训练并使用就行了 CvDTreeTrainData 这个结构体可以在内部构建和使用 但是, 对传统的树算法或者其他一些复杂的情况来说, 这个结构体就必须被精确的构建和使用, 如下所示 : 1. 结构体需要在 set_data 之后用缺省构造函数初始化 ( 或者用完整的构造函数构建 ) 参数 _shared 设置为 true 2. 用这个数据训练一棵或者多棵树, 具体见方法 CvDTree:train 3. 最后, 该结构体只能在所有使用它的树被释放后释放 5.5 CvDTree Decision tree class CvDTree : public CvStatModel public: CvDTree(); virtual ~CvDTree(); virtual bool train( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, CvDTreeParams params=cvdtreeparams() ); virtual bool train( CvDTreeTrainData* _train_data, const CvMat* _subsample_idx ); virtual CvDTreeNode* predict( const CvMat* _sample, const CvMat* _missing_data_mask=0, bool raw_mode=false ) const; virtual const CvMat* get_var_importance(); virtual void clear(); virtual void read( CvFileStorage* fs, CvFileNode* node ); virtual void write( CvFileStorage* fs, const char* name ); 247

248 // special read & write methods for trees in the tree ensembles virtual void read( CvFileStorage* fs, CvFileNode* node, CvDTreeTrainData* data ); virtual void write( CvFileStorage* fs ); const CvDTreeNode* get_root() const; int get_pruned_tree_idx() const; CvDTreeTrainData* get_data(); protected: virtual bool do_train( const CvMat* _subsample_idx ); virtual void try_split_node( CvDTreeNode* n ); virtual void split_node_data( CvDTreeNode* n ); virtual CvDTreeSplit* find_best_split( CvDTreeNode* n ); virtual CvDTreeSplit* find_split_ord_class( CvDTreeNode* n, int vi ); virtual CvDTreeSplit* find_split_cat_class( CvDTreeNode* n, int vi ); virtual CvDTreeSplit* find_split_ord_reg( CvDTreeNode* n, int vi ); virtual CvDTreeSplit* find_split_cat_reg( CvDTreeNode* n, int vi ); virtual CvDTreeSplit* find_surrogate_split_ord( CvDTreeNode* n, int vi ); virtual CvDTreeSplit* find_surrogate_split_cat( CvDTreeNode* n, int vi ); virtual double calc_node_dir( CvDTreeNode* node ); virtual void complete_node_dir( CvDTreeNode* node ); virtual void cluster_categories( const int* vectors, int vector_count, int var_count, int* sums, int k, int* cluster_labels ); virtual void calc_node_value( CvDTreeNode* node ); virtual void prune_cv(); virtual double update_tree_rnc( int T, int fold ); virtual int cut_tree( int T, int fold, double min_alpha ); virtual void free_prune_data(bool cut_tree); virtual void free_tree(); virtual void write_node( CvFileStorage* fs, CvDTreeNode* node ); virtual void write_split( CvFileStorage* fs, CvDTreeSplit* split ); virtual CvDTreeNode* read_node( CvFileStorage* fs, CvFileNode* node, CvDTreeNode* parent ); virtual CvDTreeSplit* read_split( CvFileStorage* fs, CvFileNode* node ); virtual void write_tree_nodes( CvFileStorage* fs ); virtual void read_tree_nodes( CvFileStorage* fs, CvFileNode* node ); CvDTreeNode* root; 248

249 int pruned_tree_idx; CvMat* var_importance; CvDTreeTrainData* data; }; 5.6 CvDTree::train Trains decision tree bool CvDTree::train( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, CvDTreeParams params=cvdtreeparams() ); bool CvDTree::train( CvDTreeTrainData* _train_data, const CvMat* _subsample_idx ); 在 CvDTree 中一共有两种训练方法 第一种方法从基类 CvStatModel::train 而来, 很完整 支持所有的数据方式 (_tflag=cv_row_sample and _tflag=cv_col_sample), 同时还有样本和变量子集, 缺失数据 (missing measurements), 输入和输出的变量类型的人工合并等 最后一个参数包含所有必须的训练参数, 具体参见 CvDTreeParams 的描述 第二种训练方法最多的是用在构建树集成 它采用了预构造的 CvDTreeTrainData instance 和训练集合的可选择子集 _subsample_idx 的索引值与 _sample_indx 相关, 并传递给 CvDTreeTrainData 构造函数 比如, 如果 _sample_idx=[1,5,7,100], 那么 _subsample_idx=[0,3] 就表示样本原始样本集合的 [1,100] 被使用 5.7 CvDTree::predict 返回输入向量对应的叶子结点 CvDTreeNode* CvDTree::predict( const CvMat* _sample, const CvMat* _missing_data_mask=0, bool raw_mode=false ) const; 该方法用特征向量和可选的缺失数据的掩模作为输入, 在决策树计算之后, 用到达的叶子结点作为输出 预测结果, 无论是类别标签还是预测函数值, 都在 CvDTreeNode 结构体中找到, 比如,dtree->predict(sample,mask)->value 最后一个参数通常设置为 false, 这样就表示正常的输入 如果为 true, 该方法就假定所有的离散输入变量值都已经归一化到 0<num_of_categoriesi>-1 这个区间内 ( 因为决策树在内部都用归一化的表示 ) 这对用树集成进行快速预测非常有用 对数值输入变量就不用该标记 249

250 例子, 对 Mushroom 构建树进行分类 见 mushroom.cpp, 演示了怎样构建和使用决策树 6.Boosting 一般的机器学习方法是监督学习方法 : 训练数据由输入和期望的输出组成, 然后对非训练数据进行预测输出, 也就是找出输入 x 与输出 y 之间的函数关系 F: y = F(x) 根据输出的精确特性又可以分为分类和回归 Boosting 是个非常强大的学习方法, 它也是一个监督的分类学习方法 它组合许多 弱 分类器来产生一个强大的分类器组 [HTF01] 一个弱分类器的性能只是比随机选择好一点, 因此它可以被设计的非常简单并且不会有太大的计算花费 将很多弱分类器结合起来组成一个集成的类似于 SVM 或者神经网络的强分类器 在设计 boosting 分类器的时候最常用的弱分类器是决策树 通常每个树只具有一个节点的这种最简单的决策树就足够了 Boosting 模型的学习时间里在 N 个训练样本 (xi,yi)}1n 其中 xi RK 并且 yi 1, +1} xi 是一个 K 维向量 每一维对应你所要分类的问题中的一个特征 输出的两类为 -1 和 +1 几种不同的 boosting 如离散 AdaBoost, 实数 AdaBoost, LogitBoost 和 Gentle AdaBoost[FHT98] 它们有非常类似的总体结构 因此, 我们只需要了解下面表格中最基础的两类 : 离散 AdaBoost 和实数 Adaboost 算法 为每一个样本初始化使它们具有相同的权值 (step 2) 然后一个弱分类器 f(x) 在具有权值的训练数据上进行训练 计算错误率和换算系数 cm(step 3b). 被错分的样本的权重会增加 所有的权重进行归一化, 并继续寻找若其他分类器 M-1 次 最后得到的分类器 F(x) 是这些独立的弱分类器的和的符号函数 (step 4) 1. 给定 N 样本 (xi,yi) 其中 2. 初始化权值 3. 重复 for m = 1,2,,M: 1. 根据每个训练数据的 wi 计算 2. 计算. 3. 更新权值, 并归一化使 Σiwi =

251 4. 输出分类器. 两类问题的算法 : 训练 (step 1~3) 和估计 (step 4) 注意 : 作为传统的 boosting 算法, 上述的算法只可以解决两类问题 对于多类的分类问题可以使用 AdaBoost.MH 算法将其简化为两类分类的问题, 但同时需要较大的训练数据, 可以在 [FHT98] 中找到相应的说明 为了减少计算的时间复杂度而不减少精度, 可以使用 influence trimming 方法 随着训练算法的进行和集合中树的数量的增加和信任度的增加, 大部分的训练数据被正确的分类, 从而这些样本的权重不断的降低 具有较低相关权重的样本对弱分类器的训练有较低的影响 因此这些样本会在训练分类器时排除在外而不对分类器造成较大影响 控制这个过程的参数是 weight_trim_rate 只有样本的每小部分的 weight_trim_rate 的总和较大时才会被用于弱分类器的训练 注意每个样本的系数在每个循环中被重新计算 一些已经删除的样本可能会在训练更多的分类器时被再次使用 [FHT98]. [HTF01] Hastie, T., Tibshirani, R., Friedman, J. H. The Elements of Statistical Learning: Data Mining, Inference, and Prediction. Springer Series in Statistics [FHT98] Friedman, J. H., Hastie, T. and Tibshirani, R. Additive Logistic Regression: a Statistical View of Boosting. Technical Report, Dept. of Statistics, Stanford University, CvBoostParams Boosting 训练参数 struct CvBoostParams : public CvDTreeParams int boost_type; int weak_count; int split_criteria; double weight_trim_rate; CvBoostParams(); CvBoostParams( int boost_type, int weak_count, double weight_trim_rate, int max_depth, bool use_surrogates, const float* priors ); }; boost_type Boosting type, 下面的一种 CvBoost::DISCRETE - 离散 AdaBoost CvBoost::REAL - 实数 AdaBoost CvBoost::LOGIT - LogitBoost 251

252 CvBoost::GENTLE - Gentle AdaBoost Gentle AdaBoost 和实数 AdaBoost 是最经常的选择 weak_count 建立的弱分类器的个数 split_criteria 分裂准则, 用以为弱分类树选择最优的分裂系数 构造函数 : CvBoost::DEFAULT - 为特定的 boosting 算法选择默认系数 ; 见下文 CvBoost::GINI - 使用 Gini 索引 这是实数 AdaBoost 的默认方法 ; 也可以被用做离散 AdaBoost CvBoost::MISCLASS - 使用误分类速率 这是离散 AdaBoost 的默认方法 ; 也可以被用做实数 AdaBoost CvBoost::SQERR - 使用最小二乘准则. 这是 LogitBoost 和 Gentle AdaBoost 的默认选择和唯一选择 weight_trim_rate The weight trimming ratio, 在 0 到 1 之间, 参考上面的讨论 如果这个系数小于等于 0 或者大于 1, 那么这个 trimming 将不会被使用, 所有的样本都会在每个循环中使用 其默认值为 0.95 这个结构从 CvDTreeParams 继承, 但不是所有的决策树参数都支持 特别的, cross-validation 不被支持 6.2 CvBoostTree 弱分类树 class CvBoostTree: public CvDTree public: CvBoostTree(); virtual ~CvBoostTree(); virtual bool train( CvDTreeTrainData* _train_data, const CvMat* subsample_idx, CvBoost* ensemble ); virtual void scale( double s ); virtual void read( CvFileStorage* fs, CvFileNode* node, CvBoost* ensemble, CvDTreeTrainData* _data ); virtual void clear(); protected:... CvBoost* ensemble; }; 252

253 作为一个 boost 树分类器 CvBoost 的组成部分, 这个弱分类器是从 CvDTree 派生得来的 通常, 不需要直接使用弱分类器, 虽然它们可以作为 CvBoost::weak 的元素序列通过 CvBoost::get_weak_predictions 来访问 注意 : 在 LogitBoost 和 Gentle AdaBoost 的情况下, 每一个若预测器是一个递归树, 而不是分类树 甚至在离散 AdaBoost 和实数 AdaBoost 的情况下,CvBoost::predict 的返回值 (CvDTreeNode::value) 也不是输出的类别标号 ; 一个负数代表为类别 #0, 一个正数代表类别 #1 而这些数只是权重 每一个独立的树的权重可以通过函数 CvBoostTree::scale 增加或减少 6.3 CvBoost Boost 树分类器 class CvBoost : public CvStatModel public: // Boosting type enum DISCRETE=0, REAL=1, LOGIT=2, GENTLE=3 }; // Splitting criteria enum DEFAULT=0, GINI=1, MISCLASS=3, SQERR=4 }; CvBoost(); virtual ~CvBoost(); CvBoost( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, CvBoostParams params=cvboostparams() ); virtual bool train( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, CvBoostParams params=cvboostparams(), bool update=false ); virtual float predict( const CvMat* _sample, const CvMat* _missing=0, CvMat* weak_responses=0, CvSlice slice=cv_whole_seq, bool raw_mode=false ) const; virtual void prune( CvSlice slice ); 253

254 virtual void clear(); virtual void write( CvFileStorage* storage, const char* name ); virtual void read( CvFileStorage* storage, CvFileNode* node ); CvSeq* get_weak_predictors(); const CvBoostParams& get_params() const;... protected: virtual bool set_params( const CvBoostParams& _params ); virtual void update_weights( CvBoostTree* tree ); virtual void trim_weights(); virtual void write_params( CvFileStorage* fs ); virtual void read_params( CvFileStorage* fs, CvFileNode* node ); CvDTreeTrainData* data; CvBoostParams params; CvSeq* weak;... }; 6.4 CvBoost::train 训练 boost 树分类器 bool CvBoost::train( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, CvBoostParams params=cvboostparams(), bool update=false ); 这个分类函数使用的是最通常的模板, 最后一个参数 update 表示分类器是否需要更新 ( 例如, 新的弱分类树是否被加入到已存在的总和里 ), 或者是分类器是否需要重新建立 返回必须是类别,boost 树不能用于递归的建立, 且必须是两类 注 : 如 _train_data 的维数是 data_number*featuredim, 则 _responses 的维数为 data_number*1,_var_type 的维数为 featuredim+1*1, 也就是说 _var_type 的维数是特征维数 +1 并且, 训练样本的样本数有一定的限制, 样本的个数最低为 10 个, 否则会报错 6.5 CvBoost::predict 预测对输入样本的响应 float CvBoost::predict( const CvMat* sample, const CvMat* missing=0, CvMat* weak_responses=0, CvSlice slice=cv_whole_seq, 254

255 bool raw_mode=false ) const; sample 输入样本 missing 输入的 sample 中可能存在一些缺少的测量 ( 即一部分测量可能没有观测到 ), 因此需要用一个掩模来指出这些部分 要处理缺少的测量, 弱分类器必须包含替代的方法 ( 参见 CvDTreeParams::use_surrogates) weak_reponses 可选的输出参数,weak_reponses 是一个浮点数向量, 向量中每一个浮点数都对应一个独立弱分类器的响应 向量中元素的数量必须与弱分类器的数量相等 slice 弱分类器响应中用于预测的连续子集 默认情况下使用所有的弱分类器 raw_mode 与在 CvDTree::predict 中的意思相同 一般而言被设为 false CvBoost::predict 方法通过总体的树运行样本, 返回输出的加权类标签 6.6 CvBoost::prune 移除指定的弱分类器 void CvBoost::prune( CvSlice slice ); 这个方法从结果中移除指定的弱分类器 请注意不要将这个方法与一个目前还不支持的移除单独决策树的方法混淆起来 6.7 CvBoost::get_weak_predictors 返回弱分类树的结果 CvSeq* CvBoost::get_weak_predictors(); 这个方法返回弱分类器的结果 每个元素的结果都是一个指向 CvBoostTree 类的指针 ( 或者也可能指向它的一些派生 ) 7.Random Trees Random trees have been introduced by Leo Breiman and Adele Cutler: The algorithm can deal with both classification and regression problems. Random trees is a collection (ensemble) of tree predictors that is called forest further in this section (the term has been also introduced by L. Brieman). The classification works as following: the random trees classifier takes the input feature vector, classifies it with every tree in the forest, and outputs the class label that has got the majority of "votes". 255

256 In case of regression the classifier response is the average of responses over all the trees in the forest. All the trees are trained with the same parameters, but on the different training sets, which are generated from the original training set using bootstrap procedure: for each training set we randomly select the same number of vectors as in the original set (=N). The vectors are chosen with replacement. That is, some vectors will occur more than once and some will be absent. At each node of each tree trained not all the variables are used to find the best split, rather than a random subset of them. The each node a new subset is generated, however its size is fixed for all the nodes and all the trees. It is a training parameter, set to sqrt(<number_of_variables>) by default. None of the tree built is pruned. In random trees there is no need in any accuracy estimation procedures, such as cross-validation or bootstrap, or a separate test set to get an estimate of the training error. The error is estimated internally during the training. When the training set for the current tree is drawn by sampling with replacement, some vectors are left out (so-called oob (out-of-bag) data). The size of oob data is about N/3. The classification error is estimated by using this oob-data as following: Get a prediction for each vector, which is oob relatively to the i-th tree, using the very i-th tree. After all the trees have been trained, for each vector that has ever been oob, find the class-"winner" for it (i.e. the class that has got the majority of votes in the trees, where the vector was oob) and compare it to the ground-truth response. Then the classification error estimate is computed as ratio of number of missclassified oob vectors to all the vectors in the original data. In the case of regression the oob-error is computed as the squared error for oob vectors difference divided by the total number of vectors. References: Machine Learning, Wald I, July 2002 Looking Inside the Black Box, Wald II, July 2002 Software for the Masses, Wald III, July 2002 And other articles from the web-site CvRTParams Training Parameters of Random Trees struct CvRTParams : public CvDTreeParams bool calc_var_importance; int nactive_vars; CvTermCriteria term_crit; CvRTParams() : CvDTreeParams( 5, 10, 0, false, 10, 0, false, false, 0 ), calc_var_importance(false), nactive_vars(0) 256

257 term_crit = cvtermcriteria( CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 50, 0.1 ); } CvRTParams( int _max_depth, int _min_sample_count, float _regression_accuracy, bool _use_surrogates, int _max_categories, const float* _priors, bool _calc_var_importance, int _nactive_vars, int max_tree_count, float forest_accuracy, int termcrit_type ); }; calc_var_importance If it is set, then variable importance is computed by the training procedure. To retrieve the computed variable importance array, call the method CvRTrees::get_var_importance(). nactive_vars The number of variables that are randomly selected at each tree node and that are used to find the best split(s). term_crit Termination criteria for growing the forest: term_crit.max_iter is the maximum number of trees in the forest (see also max_tree_count parameter of the constructor, by default it is set to 50) term_crit.epsilon is the sufficient accuracy (OOB error). The set of training parameters for the forest is the superset of the training parameters for a single tree. However, Random trees do not need all the functionality/features of decision trees, most noticeably, the trees are not pruned, so the cross-validation parameters are not used. CvRTrees Random Trees class CvRTrees : public CvStatModel public: CvRTrees(); virtual ~CvRTrees(); virtual bool train( const CvMat* _train_data, int _tflag, const CvMat* _responses, const CvMat* _var_idx=0, const CvMat* _sample_idx=0, const CvMat* _var_type=0, const CvMat* _missing_mask=0, CvRTParams params=cvrtparams() ); virtual float predict( const CvMat* sample, const CvMat* missing = 0 ) const; virtual void clear(); virtual const CvMat* get_var_importance(); virtual float get_proximity( const CvMat* sample_1, const CvMat* sample_2 ) const; virtual void read( CvFileStorage* fs, CvFileNode* node ); virtual void write( CvFileStorage* fs, const char* name ); CvMat* get_active_var_mask(); CvRNG* get_rng(); int get_tree_count() const; 257

258 CvForestTree* get_tree(int i) const; protected: bool grow_forest( const CvTermCriteria term_crit ); // array of the trees of the forest CvForestTree** trees; CvDTreeTrainData* data; int ntrees; int nclasses;... }; CvRTrees::train Trains Random Trees model bool CvRTrees::train( const CvMat* train_data, int tflag, const CvMat* responses, const CvMat* comp_idx=0, const CvMat* sample_idx=0, const CvMat* var_type=0, const CvMat* missing_mask=0, CvRTParams params=cvrtparams() ); The method CvRTrees::train is very similar to the first form of CvDTree::train() and follows the generic method CvStatModel::train conventions. All the specific to the algorithm training parameters are passed as CvRTParams instance. The estimate of the training error (oob-error) is stored in the protected class member oob_error. CvRTrees::predict Predicts the output for the input sample double CvRTrees::predict( const CvMat* sample, const CvMat* missing=0 ) const; The input parameters of the prediction method are the same as in CvDTree::predict, but the return value type is different. This method returns the cummulative result from all the trees in the forest (the class that receives the majority of voices, or the mean of the regression function estimates). CvRTrees::get_var_importance Retrieves the variable importance array const CvMat* CvRTrees::get_var_importance() const; The method returns the variable importance vector, computed at the training stage when CvRTParams::calc_var_importance is set. If the training flag is not set, then the NULL pointer is returned. This is unlike decision trees, where variable importance can be computed anytime after the training. CvRTrees::get_proximity Retrieves proximitity measure between two training samples 258

259 float CvRTrees::get_proximity( const CvMat* sample_1, const CvMat* sample_2 ) const; The method returns proximity measure between any two samples (the ratio of the those trees in the ensemble, in which the samples fall into the same leaf node, to the total number of the trees). Example. Prediction of mushroom edibility using random trees classifier 1. include <float.h> 2. include <stdio.h> 3. include <ctype.h> 4. include "ml.h" int main( void ) CvStatModel* cls = NULL; CvFileStorage* storage = cvopenfilestorage( "Mushroom.xml", NULL,CV_STORAGE_READ ); CvMat* data = (CvMat*)cvReadByName(storage, NULL, "sample", 0 ); CvMat train_data, test_data; CvMat response; CvMat* missed = NULL; CvMat* comp_idx = NULL; CvMat* sample_idx = NULL; CvMat* type_mask = NULL; int resp_col = 0; int i,j; CvRTreesParams params; CvTreeClassifierTrainParams cart_params; const int ntrain_samples = 1000; const int ntest_samples = 1000; const int nvars = 23; if(data == NULL data->cols!= nvars) puts("error in source data"); return -1; } cvgetsubrect( data, &train_data, cvrect(0, 0, nvars, ntrain_samples) ); cvgetsubrect( data, &test_data, cvrect(0, ntrain_samples, nvars, ntrain_samples + ntest_samples) ); resp_col = 0; cvgetcol( &train_data, &response, resp_col); /* create missed variable matrix */ missed = cvcreatemat(train_data.rows, train_data.cols, CV_8UC1); for( i = 0; i < train_data.rows; i++ ) for( j = 0; j < train_data.cols; j++ ) 259

260 CV_MAT_ELEM(*missed,uchar,i,j) = (uchar)(cv_mat_elem(train_data,float,i,j) < 0); /* create comp_idx vector */ comp_idx = cvcreatemat(1, train_data.cols-1, CV_32SC1); for( i = 0; i < train_data.cols; i++ ) if(i<resp_col)cv_mat_elem(*comp_idx,int,0,i) = i; if(i>resp_col)cv_mat_elem(*comp_idx,int,0,i-1) = i; } /* create sample_idx vector */ sample_idx = cvcreatemat(1, train_data.rows, CV_32SC1); for( j = i = 0; i < train_data.rows; i++ ) if(cv_mat_elem(response,float,i,0) < 0) continue; CV_MAT_ELEM(*sample_idx,int,0,j) = i; j++; } sample_idx->cols = j; /* create type mask */ type_mask = cvcreatemat(1, train_data.cols+1, CV_8UC1); cvset( type_mask, cvrealscalar(cv_var_categorical), 0); // initialize training parameters cvsetdefaultparamtreeclassifier((cvstatmodelparams*)&cart_params); cart_params.wrong_feature_as_unknown = 1; params.tree_params = &cart_params; params.term_crit.max_iter = 50; params.term_crit.epsilon = 0.1; params.term_crit.type = CV_TERMCRIT_ITER CV_TERMCRIT_EPS; puts("random forest results"); cls = cvcreatertreesclassifier( &train_data, CV_ROW_SAMPLE, &response, (CvStatModelParams*)& params, comp_idx, sample_idx, type_mask, missed ); if( cls ) CvMat sample = cvmat( 1, nvars, CV_32FC1, test_data.data.fl ); CvMat test_resp; int wrong = 0, total = 0; cvgetcol( &test_data, &test_resp, resp_col); for( i = 0; i < ntest_samples; i++, sample.data.fl += nvars ) if( CV_MAT_ELEM(test_resp,float,i,0) >= 0 ) float resp = cls->predict( cls, &sample, NULL ); wrong += (fabs(resp-response.data.fl[i]) > 1e-3 )? 1 : 0; total++; 260

261 } } printf( "Test set error = %.2f\n", wrong*100.f/(float)total ); } else puts("error forest creation"); cvreleasemat(&missed); cvreleasemat(&sample_idx); cvreleasemat(&comp_idx); cvreleasemat(&type_mask); cvreleasemat(&data); cvreleasestatmodel(&cls); cvreleasefilestorage(&storage); return 0; } 8.Expectation-Maximization The EM (Expectation-Maximization) algorithm estimates the parameters of the multivariate probability density function in a form of the Gaussian mixture distribution with a specified number of mixtures. Consider the set of the feature vectors x1, x2,..., xn}: N vectors from d-dimensional Euclidean space drawn from a Gaussian mixture: where m is the number of mixtures, pk is the normal distribution density with the mean ak and covariance matrix Sk, πk is the weight of k-th mixture. Given the number of mixtures m and the samples xi, i=1..n} the algorithm finds the maximum-likelihood estimates (MLE) of the all the mixture parameters, i.e. ak, Sk and πk: EM algorithm is an iterative procedure. Each iteration of it includes two steps. At the first step (Expectation-step, or E-step), we find a probability pi,k (denoted αi,k in the formula below) of sample #i to belong to mixture #k using the currently available mixture parameter estimates: 261

262 At the second step (Maximization-step, or M-step) the mixture parameter estimates are refined using the computed probabilities: Alternatively, the algorithm may start with M-step when initial values for pi,k can be provided. Another alternative, when pi,k are unknown, is to use a simpler clustering algorithm to pre-cluster the input samples and thus obtain initial pi,k. Often (and in ML) k-means algorithm is used for that purpose. One of the main that EM algorithm should deal with is the large number of parameters to estimate. The majority of the parameters sits in covariation matrices, which are d d elements each (where d is the feature space dimensionality). However, in many practical problems the covariation matrices are close to diagonal, or even to μk*i, where I is identity matrix and μk is mixture-dependent "scale" parameter. So a robust computation scheme could be to start with the harder constraints on the covariation matrices and then use the estimated parameters as an input for a less constrained optimization problem (often a diagonal covariation matrix is already a good enough approximation). References: [Bilmes98] J. A. Bilmes. A Gentle Tutorial of the EM Algorithm and its Application to Parameter Estimation for Gaussian Mixture and Hidden Markov Models. Technical Report TR , International Computer Science Institute and Computer Science Division, University of California at Berkeley, April CvEMParams EM 算法估计混合高斯模型所需要的参数 Parameters of EM algorithm struct CvEMParams CvEMParams() : nclusters(10), cov_mat_type(cvem::cov_mat_diagonal), start_step(cvem::start_auto_step), probs(0), weights(0), means(0), covs(0) term_crit=cvtermcriteria( CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 100, FLT_EPSILON ); } CvEMParams( int _nclusters, int _cov_mat_type=1/*cvem::cov_mat_diagonal*/, int _start_step=0/*cvem::start_auto_step*/, CvTermCriteria _term_crit=cvtermcriteria(cv_termcrit_iter+cv_termcrit_eps, 100, FLT_EPSILON), 262

263 CvMat* _probs=0, CvMat* _weights=0, CvMat* _means=0, CvMat** _covs=0 ) : nclusters(_nclusters), cov_mat_type(_cov_mat_type), start_step(_start_step), probs(_probs), weights(_weights), means(_means), covs(_covs), term_crit(_term_crit) } int nclusters; int cov_mat_type; int start_step; const CvMat* probs; const CvMat* weights; const CvMat* means; const CvMat** covs; CvTermCriteria term_crit; }; 8.2 nclusters 高斯成分的个数, 必须事先指定 有些 EM 的算法可以在给定区间内, 自动确定最优个数, 不过本算法未实现此功能 8.3 cov_mat_type 协变矩阵的类型, 只能为下面三种类型之一 对称矩阵每一个混合的协变矩阵都是任意的对称正定矩阵, 因此每个矩阵的自由参数为 d2/2 通常来说, 除非对参数有非常精确的估计, 或者有足够大量的样本, 否则不建议使用这个选项 对角阵 ( 假设数据各维之间是独立的, 最常用的情况 ) 每一个混合的协变矩阵都是任意的正对角阵, 也就是说, 非对角位置的值必须是 0, 因此每一个矩阵的自由参数为 d 这是一个最常用的选项, 也能得到很好的结果 球矩阵每一个混合的协变矩阵都是全 N 阵, 即 μk*i, 一次唯一的参数就是 μk 这个选项通常用在一些特殊的场合, 比如约束条件是相关的, 或者作为某些优化算法的第一步 ( 比如, 用 PCA 来预处理数据时 ) 这个预估计的结果会被再次传递到优化过程中, 比如 cov_mat_type=cvem::cov_mat_diagonal 8.4 start_step 开始步骤, 3 种选择 The initial step the algorithm starts from; should be one of the following: CvEM::START_E_STEP - the algorithm starts with E-step. At least, the initial values of mean vectors, CvEMParams::means must be passed. Optionally, the user may also 263

264 provide initial values for weights (CvEMParams::weights) and/or covariation matrices (CvEMParams::covs). CvEM::START_M_STEP - the algorithm starts with M-step. The initial probabilities pi,k must be provided. CvEM::START_AUTO_STEP - No values are required from the user, k-means algorithm is used to estimate initial mixtures parameters. 8.5 term_crit E 步和 M 步迭代停止的准则 EM 算法会在一定的迭代次数之后 (term_crit.num_iter), 或者当模型参数在两次迭代之间的变化小于预定值 (term_crit.epsilon) 时停止 8.6 probs 初始的后验概率 Initial probabilities pi,k; are used (and must be not NULL) only when start_step=cvem::start_m_step. 8.7 weights 初始的各个成分的概率 Initial mixture weights πk; are used (if not NULL) only when start_step=cvem::start_e_step. 8.8 covs 初始的协方差矩阵 Initial mixture covariation matrices Sk; are used (if not NULL) only when start_step=cvem::start_e_step. 8.9 means 初始的均值 Initial mixture means ak; are used (and must be not NULL) only when start_step=cvem::start_e_step. The structure has 2 constructors, the default one represents a rough rule-of-thumb, with another one it is possible to override a variety of parameters, from a single number of mixtures (the only essential problem-dependent parameter), to the initial values for the mixture parameters. CvEM EM model class CV_EXPORTS CvEM : public CvStatModel public: 264

265 // Type of covariation matrices enum COV_MAT_SPHERICAL=0, COV_MAT_DIAGONAL=1, COV_MAT_GENERIC=2 }; // The initial step enum START_E_STEP=1, START_M_STEP=2, START_AUTO_STEP=0 }; CvEM(); CvEM( const CvMat* samples, const CvMat* sample_idx=0, CvEMParams params=cvemparams(), CvMat* labels=0 ); virtual ~CvEM(); virtual bool train( const CvMat* samples, const CvMat* sample_idx=0, CvEMParams params=cvemparams(), CvMat* labels=0 ); virtual float predict( const CvMat* sample, CvMat* probs ) const; virtual void clear(); int get_nclusters() const return params.nclusters; } const CvMat* get_means() const return means; } const CvMat** get_covs() const return covs; } const CvMat* get_weights() const return weights; } const CvMat* get_probs() const return probs; } protected: virtual void set_params( const CvEMParams& params, const CvVectors& train_data ); virtual void init_em( const CvVectors& train_data ); virtual double run_em( const CvVectors& train_data ); virtual void init_auto( const CvVectors& samples ); virtual void kmeans( const CvVectors& train_data, int nclusters, CvMat* labels, CvTermCriteria criteria, const CvMat* means ); CvEMParams params; double log_likelihood; CvMat* means; CvMat** covs; CvMat* weights; CvMat* probs; CvMat* log_weight_div_det; CvMat* inv_eigen_values; CvMat** cov_rotate_mats; }; CvEM::train Estimates Gaussian mixture parameters from the sample set void CvEM::train( const CvMat* samples, const CvMat* sample_idx=0, 265

266 CvEMParams params=cvemparams(), CvMat* labels=0 ); Unlike many of ML models, EM is an unsupervised learning algorithm and it does not take responses (class labels or the function values) on input. Instead, it computes MLE of Gaussian mixture parameters from the input sample set, stores all the parameters inside the stucture: pi,k in probs, ak in means Sk in covs[k], πk in weights and optionally computes the output "class label" for each sample: labelsi=arg maxk(pi,k), i=1..n (i.e. indices of the most-probable mixture for each sample). The trained model can be used further for prediction, just like any other classifier. The model trained is similar to the normal bayes classifier. Example. Clustering random samples of multi-gaussian distribution using EM 1. include "ml.h" 2. include "highgui.h" int main( int argc, char** argv ) const int N = 4; const int N1 = (int)sqrt((double)n); const CvScalar colors[] = 0,0,255}},Template:0,255,0,Template:0,255,255,Template:255,255,0}; int i, j; int nsamples = 100; CvRNG rng_state = cvrng(-1); CvMat* samples = cvcreatemat( nsamples, 2, CV_32FC1 ); CvMat* labels = cvcreatemat( nsamples, 1, CV_32SC1 ); IplImage* img = cvcreateimage( cvsize( 500, 500 ), 8, 3 ); float _sample[2]; CvMat sample = cvmat( 1, 2, CV_32FC1, _sample ); CvEM em_model; CvEMParams params; CvMat samples_part; cvreshape( samples, samples, 2, 0 ); for( i = 0; i < N; i++ ) CvScalar mean, sigma; // form the training samples cvgetrows( samples, &samples_part, i*nsamples/n, (i+1)*nsamples/n ); mean = cvscalar(((i%n1)+1.)*img->width/(n1+1), ((i/n1)+1.)*img->height/(n1+1)); sigma = cvscalar(30,30); cvrandarr( &rng_state, &samples_part, CV_RAND_NORMAL, mean, sigma ); 266

267 } cvreshape( samples, samples, 1, 0 ); // initialize model's parameters params.covs = NULL; params.means = NULL; params.weights = NULL; params.probs = NULL; params.nclusters = N; params.cov_mat_type = CvEM::COV_MAT_SPHERICAL; params.start_step = CvEM::START_AUTO_STEP; params.term_crit.max_iter = 10; params.term_crit.epsilon = 0.1; params.term_crit.type = CV_TERMCRIT_ITER CV_TERMCRIT_EPS; // cluster the data em_model.train( samples, 0, params, labels ); 1. if 0 // the piece of code shows how to repeatedly optimize the model // with less-constrained parameters (COV_MAT_DIAGONAL instead of COV_MAT_SPHERICAL) // when the output of the first stage is used as input for the second. CvEM em_model2; params.cov_mat_type = CvEM::COV_MAT_DIAGONAL; params.start_step = CvEM::START_E_STEP; params.means = em_model.get_means(); params.covs = (const CvMat**)em_model.get_covs(); params.weights = em_model.get_weights(); em_model2.train( samples, 0, params, labels ); // to use em_model2, replace em_model.predict() with em_model2.predict() below 1. endif // classify every image pixel cvzero( img ); for( i = 0; i < img->height; i++ ) for( j = 0; j < img->width; j++ ) CvPoint pt = cvpoint(j, i); sample.data.fl[0] = (float)j; sample.data.fl[1] = (float)i; int response = cvround(em_model.predict( &sample, NULL )); CvScalar c = colors[response]; 267

268 cvcircle( img, pt, 1, cvscalar(c.val[0]*0.75,c.val[1]*0.75,c.val[2]*0.75), CV_FILLED ); } } //draw the clustered samples for( i = 0; i < nsamples; i++ ) CvPoint pt; pt.x = cvround(samples->data.fl[i*2]); pt.y = cvround(samples->data.fl[i*2+1]); cvcircle( img, pt, 1, colors[labels->data.i[i]], CV_FILLED ); } cvnamedwindow( "EM-clustering result", 1 ); cvshowimage( "EM-clustering result", img ); cvwaitkey(0); cvreleasemat( &samples ); cvreleasemat( &labels ); return 0; } 9. 神经网络 ML 实现了前馈 (feedforward) 人工神经元网络 (ANN), 准确地说是最常用的神经元网络, multi-layerperceptrons (MLP) MLP 由输入层 输出层和一个或多个隐藏层构成 MLP 的每一层包含了一个或多个神经元, 它们从之前的层和之后的层的神经元有向的连接在一起 下面是一个三层感知器 (perceptron) 的例子, 其由 3 个输入 2 个输出以及包含 5 个神经元的隐藏层构成 : MLP 中所有的神经元都是相似的 每一个有多个输入链路 ( 比如, 取前一层的多个神经元的输出作为自己的输入 ) 和多个输出链路 ( 比如传递响应到下一层的多个神经元 ) In other words, given the outputs xj} of the layer n, the outputs yi} of the layer n+1 are computed as: ui=sumj(w(n+1)i,j*xj) + w(n+1)i,bias yi=f(ui) Different activation functions may be used, the ML implements 3 standard ones: Identity function (CvANN_MLP::IDENTITY): f(x)=x Symmetrical sigmoid (CvANN_MLP::SIGMOID_SYM): f(x)=β*(1-e-αx)/(1+e-αx), the default choice for MLP; the standard sigmoid with β=1, α=1 is shown below: Gaussian function 268

269 (CvANN_MLP::GAUSSIAN): f(x)=βe-αx*x, not completely supported by the moment. In ML all the neurons have the same activation functions, with the same free parameters (α, β) that are specified by user and are not altered by the training algorithms. So the whole trained network works as following. It takes the feature vector on input, the vector size is equal to the size of the input layer, when the values are passed as input to the first hidden layer, the outputs of the hidden layer are computed using the weights and the activation functions and passed further downstream, until we compute the output layer. So, in order to compute the network one need to know all the weights w(n+1)i,j. The weights are computed by the training algorithm. The algorithm takes a training set: multiple input vectors with the corresponding output vectors, and iteratively adjusts the weights to try to make the network give the desired response on the provided input vectors. The larger the network size (the number of hidden layers and their sizes), the more is the potential network flexibility, and the error on the training set could be made arbitrarily small. But at the same time the learned network will also "learn" the noise present in the training set, so the error on the test set usually starts increasing after the network size reaches some limit. Besides, the larger networks are train much longer than the smaller ones, so it is reasonable to preprocess the data (using PCA or similar technique) and train a smaller network on only the essential features. Another feature of the MLP's is their inability to handle categorical data as is, however there is a workaround. If a certain feature in the input or output (i.e. in case of n-class classifier for n>2) layer is categorical and can take M (>2) different values, it makes sense to represent it as binary tuple of M elements, where i-th element is 1 if and only if the feature is equal to the i-th value out of M possible. It will increase the size of the input/output layer, but will speedup the training algorithm convergence and at the same time enable "fuzzy" values of such variables, i.e. a tuple of probabilities instead of a fixed value. ML implements 2 algorithms for training MLP's. The first is the classical random sequential backpropagation algorithm and the second (default one) is batch RPROP algorithm References: Wikipedia article about the backpropagation algorithm. Y. LeCun, L. Bottou, G.B. Orr and K.-R. Muller, "Efficient backprop", in Neural Networks---Tricks of the Trade, Springer Lecture Notes in Computer Sciences 1524, pp.5-50, M. Riedmiller and H. Braun, "A Direct 269

270 Adaptive Method for Faster Backpropagation Learning: The RPROP Algorithm", Proc. ICNN, San Fransisco (1993). CvANN_MLP_TrainParams Parameters of MLP training algorithm struct CvANN_MLP_TrainParams CvANN_MLP_TrainParams(); CvANN_MLP_TrainParams( CvTermCriteria term_crit, int train_method, double param1, double param2=0 ); ~CvANN_MLP_TrainParams(); enum BACKPROP=0, RPROP=1 }; CvTermCriteria term_crit; int train_method; // backpropagation parameters double bp_dw_scale, bp_moment_scale; // rprop parameters double rp_dw0, rp_dw_plus, rp_dw_minus, rp_dw_min, rp_dw_max; }; term_crit The termination criteria for the training algorithm. Identifies how many iteration is done by the algorithm (for sequential backpropagation algorithm the number is multiplied by the size of the training set) and how much the weights could change between the iterations to make the algorithm continue. train_method The training algoithm to use; can be one of CvANN_MLP_TrainParams::BACKPROP (sequential backpropagation algorithm) or CvANN_MLP_TrainParams::RPROP (RPROP algorithm, default value). bp_dw_scale (Backpropagation only): The coefficient to multiply the computed weight gradient by. The recommended value is about 0.1. The parameter can be set via param1 of the constructor. bp_moment_scale (Backpropagation only): The coefficient to multiply the difference between weights on the 2 previous iterations. Provides some inertia to smooth the random fluctuations of the weights. Can vary from 0 (the feature is disabled) to 1 and beyond. The value 0.1 or so is good enough. The parameter can be set via param2 of the constructor. rp_dw0 (RPROP only): Initial magnitude of the weight delta. The default value is 0.1. The parameter can be set via param1 of the constructor. rp_dw_plus (RPROP only): The increase factor for the weight delta. Must be >1, default value is 1.2 that should work well in most cases, according to the algorithm author. The parameter can only be changed 270

271 explicitly by modifying the structure member. rp_dw_minus (RPROP only): The decrease factor for the weight delta. Must be <1, default value is 0.5 that should work well in most cases, according to the algorithm author. The parameter can only be changed explicitly by modifying the structure member. rp_dw_min (RPROP only): The minimum value of the weight delta. Must be >0, the default value is FLT_EPSILON. The parameter can be set via param2 of the constructor. rp_dw_max (RPROP only): The maximum value of the weight delta. Must be >1, the default value is 50. The parameter can only be changed explicitly by modifying the structure member. The structure has default constructor that initalizes parameters for RPROP algorithm. There is also more advanced constructor to customize the parameters and/or choose backpropagation algorithm. Finally, the individial parameters can be adjusted after the structure is created. CvANN_MLP MLP model class CvANN_MLP : public CvStatModel public: CvANN_MLP(); CvANN_MLP( const CvMat* _layer_sizes, int _activ_func=sigmoid_sym, double _f_param1=0, double _f_param2=0 ); virtual ~CvANN_MLP(); virtual void create( const CvMat* _layer_sizes, int _activ_func=sigmoid_sym, double _f_param1=0, double _f_param2=0 ); virtual int train( const CvMat* _inputs, const CvMat* _outputs, const CvMat* _sample_weights, const CvMat* _sample_idx=0, CvANN_MLP_TrainParams _params = CvANN_MLP_TrainParams(), int flags=0 ); virtual float predict( const CvMat* _inputs, CvMat* _outputs ) const; virtual void clear(); 271

272 // possible activation functions enum IDENTITY = 0, SIGMOID_SYM = 1, GAUSSIAN = 2 }; // available training flags enum UPDATE_WEIGHTS = 1, NO_INPUT_SCALE = 2, NO_OUTPUT_SCALE = 4 }; virtual void read( CvFileStorage* fs, CvFileNode* node ); virtual void write( CvFileStorage* storage, const char* name ); int get_layer_count() return layer_sizes? layer_sizes->cols : 0; } const CvMat* get_layer_sizes() return layer_sizes; } protected: virtual bool prepare_to_train( const CvMat* _inputs, const CvMat* _outputs, const CvMat* _sample_weights, const CvMat* _sample_idx, CvANN_MLP_TrainParams _params, CvVectors* _ivecs, CvVectors* _ovecs, double** _sw, int _flags ); // sequential random backpropagation virtual int train_backprop( CvVectors _ivecs, CvVectors _ovecs, const double* _sw ); // RPROP algorithm virtual int train_rprop( CvVectors _ivecs, CvVectors _ovecs, const double* _sw ); virtual void calc_activ_func( CvMat* xf, const double* bias ) const; virtual void calc_activ_func_deriv( CvMat* xf, CvMat* deriv, const double* bias ) const; virtual void set_activ_func( int _activ_func=sigmoid_sym, double _f_param1=0, double _f_param2=0 ); virtual void init_weights(); 272

273 virtual void scale_input( const CvMat* _src, CvMat* _dst ) const; virtual void scale_output( const CvMat* _src, CvMat* _dst ) const; virtual void calc_input_scale( const CvVectors* vecs, int flags ); virtual void calc_output_scale( const CvVectors* vecs, int flags ); virtual void write_params( CvFileStorage* fs ); virtual void read_params( CvFileStorage* fs, CvFileNode* node ); CvMat* layer_sizes; CvMat* wbuf; CvMat* sample_weights; double** weights; double f_param1, f_param2; double min_val, max_val, min_val1, max_val1; int activ_func; int max_count, max_buf_sz; CvANN_MLP_TrainParams params; CvRNG rng; }; Unlike many other models in ML that are constructed and trained at once, in MLP these steps are separated. First, a network with the specified topology is created using the non-default constructor or the method create. All the weights are set to zero's. Then the network is trained using the set of input and output vectors. The training procedure can be repeated more than once, i.e. the weights can be adjusted based on the new training data. CvANN_MLP::create Constructs the MLP with the specified topology void CvANN_MLP::create( const CvMat* _layer_sizes, int _activ_func=sigmoid_sym, 273

274 double _f_param1=0, double _f_param2=0 ); _layer_sizes The integer vector, specifying the number of neurons in each layer, including the input and the output ones. _activ_func Specifies the activation function for each neuron; one of CvANN_MLP::IDENTITY, CvANN_MLP::SIGMOID_SYM and CvANN_MLP::GAUSSIAN. _f_param1, _f_param2 Free parameters of the activation function, α and β, respectively. See the formulas in the introduction section. The method creates MLP network with the specified topology and assigns the same activation function to all the neurons. CvANN_MLP::train Trains/updates MLP int CvANN_MLP::train( const CvMat* _inputs, const CvMat* _outputs, const CvMat* _sample_weights, const CvMat* _sample_idx=0, CvANN_MLP_TrainParams _params = CvANN_MLP_TrainParams(), int flags=0 ); _inputs A floating-point matrix of input vectors, one vector per row. _outputs A floating-point matrix of the corresponding output vectors, one vector per row. _sample_weights (RPROP only) The optional floating-point vector of weights for each sample. Some samples may be more important than others for training, e.g. user may want to gain the weight of certain classes to find the right balance between hit-rate and false-alarm rate etc. _sample_idx The optional integer vector indicating the samples (i.e. rows of _inputs and _outputs) that are taken into account. _params The training params. See CvANN_MLP_TrainParams description. _flags The various algorithm parameters. May be a combination of the following: UPDATE_WEIGHTS = 1 - update the network weights, rather than compute them from scratch (in the latter case the weights are intialized using Nguyen-Widrow algorithm). NO_INPUT_SCALE - do not normalize the input vectors. If the flag is not set, the training algorithm normalizes each input feature independently, shifting its mean value to 0 and making the standard deviation =1. If the network is assumed to be updated frequently, the new training data should be much different from original one. In this case user should take care of proper normalization. NO_OUTPUT_SCALE - do not normalize the output vectors. If the flag is not set, the training algorithm normalizes each output features independently, by trasforming it to the certain range depending on the activation function used. The method applies the specified training algorithm to compute/adjust the network weights. It returns the number of iterations done. 274

275 275

276 CvAux 中文参考手册 1. 立体匹配 1.1 FindStereoCorrespondence 计算一对校正好的图像的视差图 cvfindstereocorrespondence( const CvArr* leftimage, const CvArr* rightimage, int mode, CvArr* depthimage, int maxdisparity, double param1, double param2, double param3, double param4, double param5 ); leftimage:: 左图, 必须为 8 位的灰度图 rightimage:: 右图, 必须为 8 位的灰度图 mode:: 指定采用的算法 ( 当前只支持 CV_DISPARITY_BIRCHFIELD ) depthimage:: 输出的视差图, 8 位的灰度图 maxdisparity:: 指定最大的可能差异 ( 视差 ). 物体越近视差越大. param1, param2, param3, param4, param5:: - 算法的参数,param1 为遮挡时的处罚值 (constant occlusion penalty), param2 为匹配时的奖励值, param3 定义高可靠区域 (set of contiguous pixels whose reliability is at least param3), param4 定义比较可靠区域 defines a moderately reliable region, param5 定义有些可靠的区域 defines a slightly reliable region. 如果省略一些参数就会采用默认值. 在 Birchfield 算法中 param1 = 25, param2 = 5, param3 = 12, param4 = 15, param5 = 25 ( 这些数值来自书籍 "Depth Discontinuities by Pixel-to-Pixel Stereo" Stanford University Technical Report STAN-CS-TR , July 1996.) 函数 cvfindstereocorrespondence 计算两个校正后的灰度图像的视差图 例子. 计算一对图像的视差 /* */ IplImage* srcleft = cvloadimage("left.jpg",1); IplImage* srcright = cvloadimage("right.jpg",1); IplImage* leftimage = cvcreateimage(cvgetsize(srcleft), IPL_DEPTH_8U, 1); IplImage* rightimage = cvcreateimage(cvgetsize(srcright), IPL_DEPTH_8U, 1); 276

277 IplImage* depthimage = cvcreateimage(cvgetsize(srcright), IPL_DEPTH_8U, 1); cvcvtcolor(srcleft, leftimage, CV_BGR2GRAY); cvcvtcolor(srcright, rightimage, CV_BGR2GRAY); cvfindstereocorrespondence( leftimage, rightimage, CV_DISPARITY_BIRCHFIELD, depthimage, 50, 15, 3, 6, 8, 15 ); /* */ 本例子使用的图片可在以下地址下载 View Morphing Functions 2.1 MakeScanlines Calculates scanlines coordinates for two cameras by fundamental matrix void cvmakescanlines( const CvMatrix3* matrix, CvSize img_size, int* scanlines1, int* scanlines2, int* lengths1, int* lengths2, int* line_count ); matrix:: Fundamental matrix.imgsize:: Size of the image.scanlines1:: Pointer to the array of calculated scanlines of the first image.scanlines2:: Pointer to the array of calculated scanlines of the second image.lengths1:: Pointer to the array of calculated lengths (in pixels) of the first image scanlines.lengths2:: Pointer to the array of calculated lengths (in pixels) of the second image scanlines.line_count:: Pointer to the variable that stores the number of scanlines. The function cvmakescanlines finds coordinates of scanlines for two images. This function returns the number of scanlines. The function does nothing except calculating the number of scanlines if the pointers scanlines1 or scanlines2 are equal to zero. 2.2 PreWarpImage Rectifies image void cvprewarpimage( int line_count, IplImage* img, uchar* dst, int* dst_nums, int* scanlines ); 277

278 line_count:: Number of scanlines for the image.img:: Image to prewarp.dst:: Data to store for the prewarp image.dst_nums:: Pointer to the array of lengths of scanlines.scanlines:: Pointer to the array of coordinates of scanlines. The function cvprewarpimage rectifies the image so that the scanlines in the rectified image are horizontal. The output buffer of size max(width,height)*line_count*3 must be allocated before calling the function. 2.3 FindRuns Retrieves scanlines from rectified image and breaks them down into runs void cvfindruns( int line_count, uchar* prewarp1, uchar* prewarp2, int* line_lengths1, int* line_lengths2, int* runs1, int* runs2, int* num_runs1, int* num_runs2 ); line_count:: Number of the scanlines.prewarp1:: Prewarp data of the first image.prewarp2:: Prewarp data of the second image.line_lengths1:: Array of lengths of scanlines in the first image.line_lengths2:: Array of lengths of scanlines in the second image.runs1:: Array of runs in each scanline in the first image.runs2:: Array of runs in each scanline in the second image.num_runs1:: Array of numbers of runs in each scanline in the first image.num_runs2:: Array of numbers of runs in each scanline in the second image. The function cvfindruns retrieves scanlines from the rectified image and breaks each scanline down into several runs, that is, series of pixels of almost the same brightness. 2.4 DynamicCorrespondMulti Finds correspondence between two sets of runs of two warped images void cvdynamiccorrespondmulti( int line_count, int* first, int* first_runs, int* second, int* second_runs, int* first_corr, int* second_corr ); line_count:: Number of scanlines.first:: Array of runs of the first image.first_runs:: Array of numbers of runs in each scanline of the first 278

279 image.second:: Array of runs of the second image.second_runs:: Array of numbers of runs in each scanline of the second image.first_corr:: Pointer to the array of correspondence information found for the first runs.second_corr:: Pointer to the array of correspondence information found for the second runs. The function cvdynamiccorrespondmulti finds correspondence between two sets of runs of two images. Memory must be allocated before calling this function. Memory size for one array of correspondence information is max( width,height )* numscanlines*3*sizeof ( int ) 2.5 MakeAlphaScanlines Calculates coordinates of scanlines of image from virtual camera void cvmakealphascanlines( int* scanlines1, int* scanlines2, int* scanlinesa, int* lengths, int line_count, float alpha ); scanlines1:: Pointer to the array of the first scanlines.scanlines2:: Pointer to the array of the second scanlines.scanlinesa:: Pointer to the array of the scanlines found in the virtual image.lengths:: Pointer to the array of lengths of the scanlines found in the virtual image.line_count:: Number of scanlines.alpha:: Position of virtual camera ( ). The function cvmakealphascanlines finds coordinates of scanlines for the virtual camera with the given camera position. Memory must be allocated before calling this function. Memory size for the array of correspondence runs is numscanlines*2*4*sizeof(int). Memory size for the array of the scanline lengths is numscanlines*2*4*sizeof(int) 2.6 MorphEpilinesMulti Morphs two pre-warped images using information about stereo correspondence void cvmorphepilinesmulti( int line_count, uchar* first_pix, int* first_num, uchar* second_pix, int* second_num, uchar* dst_pix, int* dst_num, float alpha, int* first, int* first_runs, int* second, int* second_runs, int* first_corr, int* second_corr ); 279

280 line_count:: Number of scanlines in the prewarp image.first_pix:: Pointer to the first prewarp image.first_num:: Pointer to the array of numbers of points in each scanline in the first image.second_pix:: Pointer to the second prewarp image.second_num:: Pointer to the array of numbers of points in each scanline in the second image.dst_pix:: Pointer to the resulting morphed warped image.dst_num:: Pointer to the array of numbers of points in each line.alpha:: Virtual camera position ( ).first:: First sequence of runs.first_runs:: Pointer to the number of runs in each scanline in the first image.second:: Second sequence of runs.second_runs:: Pointer to the number of runs in each scanline in the second image.first_corr:: Pointer to the array of correspondence information found for the first runs.second_corr:: Pointer to the array of correspondence information found for the second runs. The function cvmorphepilinesmulti morphs two pre-warped images using information about correspondence between the scanlines of two images. 2.7 PostWarpImage Warps rectified morphed image back void cvpostwarpimage( int line_count, uchar* src, int* src_nums, IplImage* img, int* scanlines ); line_count:: Number of the scanlines.src:: Pointer to the prewarp image virtual image.src_nums:: Number of the scanlines in the image.img:: Resulting unwarp image.scanlines:: Pointer to the array of scanlines data. The function cvpostwarpimage warps the resultant image from the virtual camera by storing its rows across the scanlines whose coordinates are calculated by cvmakealphascanlines. 2.8 DeleteMoire Deletes moire in given image void cvdeletemoire( IplImage* img ); img:: Image. The function cvdeletemoire deletes moire from the given image. The post-warped image may have black (un-covered) points because of possible holes between neighboring scanlines. The function deletes 280

281 moire (black pixels) from the image by substituting neighboring pixels for black pixels. If all the scanlines are horizontal, the function may be omitted. 3.3D Tracking Functions The section discusses functions for tracking objects in 3d space using a stereo camera. Besides C API, there is DirectShow 3dTracker filter and the wrapper application 3dTracker. Here you may find a description how to test the filter on sample data dTrackerCalibrateCameras Simultaneously determines position and orientation of multiple cameras CvBool cv3dtrackercalibratecameras(int num_cameras, const Cv3dTrackerCameraIntrinsics camera_intrinsics[], CvSize checkerboard_size, IplImage *samples[], Cv3dTrackerCameraInfo camera_info[]); num_cameras:: the number of cameras to calibrate. This is the size of each of the three array parameters.camera_intrinsics:: camera intrinsics for each camera, such as determined by CalibFilter.checkerboard_size:: the width and height (in number of squares) of the checkerboard.samples:: images from each camera, with a view of the checkerboard.camera_info:: filled in with the results of the camera calibration. This is passed into 3dTrackerLocateObjects to do tracking. The function cv3dtrackercalibratecameras searches for a checkerboard of the specified size in each of the images. For each image in which it finds the checkerboard, it fills in the corresponding slot in camera_info with the position and orientation of the camera relative to the checkerboard and sets the valid flag. If it finds the checkerboard in all the images, it returns true; otherwise it returns false. This function does not change the members of the camera_info array that correspond to images in which the checkerboard was not found. This allows you to calibrate each camera independently, instead of simultaneously. To accomplish this, do the following: 1. clear all the valid flags before calling this function the first time; 1. call this function with each set of images; 281

282 1. check all the valid flags after each call. When all the valid flags are set, calibration is complete.. Note that this method works well only if the checkerboard is rigidly mounted; if it is handheld, all the cameras should be calibrated simultanously to get an accurate result. To ensure that all cameras are calibrated simultaneously, ignore the valid flags and use the return value to decide when calibration is complete dTrackerLocateObjects Determines 3d location of tracked objects int cv3dtrackerlocateobjects(int num_cameras, int num_objects, const Cv3dTrackerCameraInfo camera_info[], const Cv3dTracker2dTrackedObject tracking_info[], Cv3dTrackerTrackedObject tracked_objects[]); num_cameras:: the number of cameras.num_objects:: the maximum number of objects found by any camera. (Also the maximum number of objects returned in tracked_objects.)camera_info:: camera position and location information for each camera, as determined by 3dTrackerCalibrateCameras.tracking_info:: the 2d position of each object as seen by each camera. Although this is specified as a one-dimensional array, it is actually a two-dimensional array: const Cv3dTracker2dTrackedObject tracking_info[num_cameras][num_objects]. The id field of any unused slots must be -1. Ids need not be ordered or consecutive.tracked_objects:: filled in with the results. The function cv3dtrackerlocateobjects determines the 3d position of tracked objects based on the 2d tracking information from multiple cameras and the camera position and orientation information computed by 3dTrackerCalibrateCameras. It locates any objects with the same id that are tracked by more than one camera. It fills in the tracked_objects array and returns the number of objects located. The id fields of any unused slots in tracked_objects are set to

283 4.Eigen Objects (PCA) Functions The functions described in this section do PCA analysis and compression for a set of 8-bit images that may not fit into memory all together. If your data fits into memory and the vectors are not 8-bit (or you want a simpler interface), use cvcalccovarmatrix, cvsvd and cvgemm to do PCA 4.1 CalcCovarMatrixEx Calculates covariance matrix for group of input objects void cvcalccovarmatrixex( int object_count, void* input, int io_flags, int iobuf_size, uchar* buffer, void* userdata, IplImage* avg, float* covar_matrix ); object_count:: Number of source objects.input:: Pointer either to the array of IplImage input objects or to the read callback function according to the value of the parameter ioflags.io_flags:: Input/output flags.iobuf_size:: Input/output buffer size.buffer:: Pointer to the input/output buffer.userdata:: Pointer to the structure that contains all necessary data for thecallback:: functions.avg:: Averaged object.covar_matrix:: Covariance matrix. An output parameter; must be allocated before the call. The function cvcalccovarmatrixex calculates a covariance matrix of the input objects group using previously calculated averaged object. Depending on ioflags parameter it may be used either in direct access or callback mode. If ioflags is not CV_EIGOBJ_NO_CALLBACK, buffer must be allocated before calling the function. 4.2 CalcEigenObjects Calculates orthonormal eigen basis and averaged object for group of input objects void cvcalceigenobjects( int nobjects, void* input, void* output, int ioflags, int iobufsize, void* userdata, CvTermCriteria* calclimit, IplImage* avg, float* eigvals ); nobjects:: Number of source objects. 283

284 input:: Pointer either to the array of IplImage input objects or to the read callback function according to the value of the parameter ioflags output:: Pointer either to the array of eigen objects or to the write callback function according to the value of the parameter ioflags.ioflags:: Input/output flags.iobufsize:: Input/output buffer size in bytes. The size is zero, if unknown.userdata:: Pointer to the structure that contains all necessary data for the callback functions.calclimit:: Criteria that determine when to stop calculation of eigen objects.avg:: Averaged object.eigvals:: Pointer to the eigenvalues array in the descending order; may be <pre>null. The function cvcalceigenobjects calculates orthonormal eigen basis and the averaged object for a group of the input objects. Depending on ioflags parameter it may be used either in direct access or callback mode. Depending on the parameter calclimit, calculations are finished either after first calclimit.max_iter dominating eigen objects are retrieved or if the ratio of the current eigenvalue to the largest eigenvalue comes down to calclimit.epsilon threshold. The value calclimit -> type must be CV_TERMCRIT_NUMB, CV_TERMCRIT_EPS, or CV_TERMCRIT_NUMB CV_TERMCRIT_EPS. The function returns the real values calclimit->max_iter and calclimit->epsilon. The function also calculates the averaged object, which must be created previously. Calculated eigen objects are arranged according to the corresponding eigenvalues in the descending order.. The parameter eigvals may be equal to NULL, if eigenvalues are not needed. The function cvcalceigenobjects 284

285 uses the function cvcalccovarmatrixex. 4.3 CalcDecompCoeff Calculates decomposition coefficient of input object double cvcalcdecompcoeff( IplImage* obj, IplImage* eigobj, IplImage* avg ); obj:: Input object.eigobj:: Eigen object.avg:: Averaged object. The function cvcalcdecompcoeff calculates one decomposition coefficient of the input object using the previously calculated eigen object and the averaged object. 4.4 EigenDecomposite Calculates all decomposition coefficients for input object void cveigendecomposite( IplImage* obj, int eigenvec_count, void* eiginput, int ioflags, void* userdata, IplImage* avg, float* coeffs ); obj:: Input object.eigenvec_count:: Number of eigen objects.eiginput:: Pointer either to the array of IplImage input objects or to the read callback function according to the value of the parameter ioflags.ioflags:: Input/output flags.userdata:: Pointer to the structure that contains all necessary data for the callback functions.avg:: Averaged object.coeffs:: Calculated coefficients; an output parameter. The function cveigendecomposite calculates all decomposition coefficients for the input object using the previously calculated eigen objects basis and the averaged object. Depending on ioflags parameter it may be used either in direct access or callback mode. 4.5 EigenProjection Calculates object projection to the eigen sub-space void cveigenprojection( void* input_vecs, int eigenvec_count, int io_flags, void* userdata, float* coeffs, IplImage* avg, IplImage* proj ); input_vec:: Pointer to either an array of IplImage 285

286 input objects or to a callback function, depending on io_flags.eigenvec_count:: Number of eigenvectors.io_flags:: Input/output flags; see cvcalceigenobjects.userdata:: Pointer to the structure that contains all necessary data for the callback functions.coeffs:: Previously calculated decomposition coefficients.avg:: Average vector, calculated by cvcalceigenobjects.proj:: Projection to the eigen sub-space. The function cveigenprojection calculates an object projection to the eigen sub-space or, in other words, restores an object using previously calculated eigen objects basis, averaged object, and decomposition coefficients of the restored object. Depending on io_flags parameter it may be used either in direct access or callback mode. 5.Embedded Hidden Markov Models Functions In order to support embedded models the user must define structures to represent 1D HMM and 2D embedded HMM model. 5.1 CvHMM Embedded HMM Structure typedef struct _CvEHMM int level; int num_states; float* transp; float** obsprob; union CvEHMMState* state; struct _CvEHMM* ehmm; } u; } CvEHMM; level:: Level of embedded HMM. If level ==0, HMM is most external. In 2D HMM there are two types of HMM: 1 external and several embedded. External HMM has level ==1, embedded HMMs have level ==0 286

287 .num_states:: Number of states in 1D HMM.transP:: State-to-state transition probability, square matrix (num_state num_state ).obsprob:: Observation probability matrix.state:: Array of HMM states. For the last-level HMM, that is, an HMM without embedded HMMs, HMM states are real.ehmm:: Array of embedded HMMs. If HMM is not last-level, then HMM states are not real and they are HMMs. For representation of observations the following structure is defined: 5.2 CvImgObsInfo Image Observation Structure typedef struct CvImgObsInfo int obs_x; int obs_y; int obs_size; float** obs; int* state; int* mix; } CvImgObsInfo; obs_x:: Number of observations in the horizontal direction.obs_y:: Number of observations in the vertical direction.obs_size:: Length of every observation vector.obs:: Pointer to observation vectors stored consequently. Number of vectors is obs_x*obs_y.state:: Array of indices of states, assigned to every observation vector.mix:: Index of mixture component, corresponding to the observation vector within an assigned state. obs_x 水平方向的观测向量,obs_ 垂直方向的观测和向量 obs_size: 每个观测向量的长度 obs 指向储存观测向量数 : 大小是 :obs_x*obs_y state: 状态列, 赋值给第个观测各量 mix: 各部分组成的索引 确保观测向量在一个指派值内 5.3 Create2DHMM Creates 2D embedded HMM CvEHMM* cvcreate2dhmm( int* statenumber, int* nummix, int obssize ); 287

288 statenumber:: Array, the first element of the which specifies the number of superstates in the HMM. All subsequent elements specify the number of states in every embedded HMM, corresponding to each superstate. So, the length of the array is statenumber [0]+1.numMix:: Array with numbers of Gaussian mixture components per each internal state. The number of elements in the array is equal to number of internal states in the HMM, that is, superstates are not counted here.obssize:: Size of observation vectors to be used with created HMM. The function cvcreate2dhmm returns the created structure of the type CvEHMM with specified parameters. 5.4 Release2DHMM Releases 2D embedded HMM void cvrelease2dhmm(cvehmm** hmm ); hmm:: Address of pointer to HMM to be released. The function cvrelease2dhmm frees all the memory used by HMM and clears the pointer to HMM. 5.5 CreateObsInfo Creates structure to store image observation vectors CvImgObsInfo* cvcreateobsinfo( CvSize numobs, int obssize ); numobs:: Numbers of observations in the horizontal and vertical directions. For the given image and scheme of extracting observations the parameter can be computed via the macro CV_COUNT_OBS( roi, dctsize, delta, numobs ), where roi, dctsize, delta, numobs are the pointers to structures of the type CvSize. The pointer roi means size of roi of image observed, numobs is the output parameter of the macro.obssize:: Size of observation vectors to be stored in the structure. The function cvcreateobsinfo creates new structures to store image observation vectors. For definitions of the parameters 288

289 roi, dctsize, and delta see the specification of The function cvimgtoobs_dct numobs:: 一个储存着水平分量与垂直分量的数组 5.6 ReleaseObsInfo Releases observation vectors structure void cvreleaseobsinfo( CvImgObsInfo** obsinfo ); obsinfo:: Address of the pointer to the structure CvImgObsInfo. The function cvreleaseobsinfo frees all memory used by observations and clears pointer to the structure CvImgObsInfo. 5.7 ImgToObs_DCT Extracts observation vectors from image void cvimgtoobs_dct( IplImage* image, float* obs, CvSize dctsize, CvSize obssize, CvSize delta ); image:: Input image.obs:: Pointer to consequently stored observation vectors.dctsize:: Size of image blocks for which DCT (Discrete Cosine Transform) coefficients are to be computed.obssize:: Number of the lowest DCT coefficients in the horizontal and vertical directions to be put into the observation vector.delta:: Shift in pixels between two consecutive image blocks in the horizontal and vertical directions. The function cvimgtoobs_dct extracts observation vectors, that is, DCT coefficients, from the image. The user must pass obsinfo.obs as the parameter obs to use this function with other HMM functions and use the structure obsinfo 289

290 of the CvImgObsInfo type. Calculating Observations for HMM CvImgObsInfo* obs_info;... cvimgtoobs_dct( image,obs_info->obs, //!!! dctsize, obssize, delta ); 5.8 UniformImgSegm Performs uniform segmentation of image observations by HMM states void cvuniformimgsegm( CvImgObsInfo* obsinfo, CvEHMM* hmm ); obsinfo:: Observations structure.hmm:: HMM structure. The function cvuniformimgsegm segments image observations by HMM states uniformly (see Initial Segmentation for 2D Embedded HMM for 2D embedded HMM with 5 superstates and 3, 6, 6, 6, 3 internal states of every corresponding superstate). Initial Segmentation for 2D Embedded HMM 5.9 InitMixSegm Segments all observations within every internal state of HMM by state mixture components void cvinitmixsegm( CvImgObsInfo** obsinfoarray, int numimg, CvEHMM* hmm ); obsinfoarray:: Array of pointers to the observation structures.numimg:: Length of above array.hmm:: HMM. The function cvinitmixsegm takes a group of observations from several training images already segmented by states and splits a set of observation vectors within every internal HMM state into as many clusters as the number of mixture components in the state EstimateHMMStateParams Estimates all parameters of every HMM state 290

291 void cvestimatehmmstateparams( CvImgObsInfo** obsinfoarray, int numimg, CvEHMM* hmm ); obsinfoarray:: Array of pointers to the observation structures.numimg:: Length of the array.hmm:: HMM. The function cvestimatehmmstateparams computes all inner parameters of every HMM state, including Gaussian means, variances, etc EstimateTransProb Computes transition probability matrices for embedded HMM void cvestimatetransprob( CvImgObsInfo** obsinfoarray, int numimg, CvEHMM* hmm ); obsinfoarray:: Array of pointers to the observation structures.numimg:: Length of the above array.hmm:: HMM. The function cvestimatetransprob uses current segmentation of image observations to compute transition probability matrices for all embedded and external HMMs EstimateObsProb Computes probability of every observation of several images void cvestimateobsprob( CvImgObsInfo* obsinfo, CvEHMM* hmm ); obsinfo:: Observation structure.hmm:: HMM structure. The function cvestimateobsprob computes Gaussian probabilities of each observation to occur in each of the internal HMM states EViterbi Executes Viterbi algorithm for embedded HMM float cveviterbi( CvImgObsInfo* obsinfo, CvEHMM* hmm ); obsinfo:: Observation structure.hmm:: HMM structure. 291

292 The function cveviterbi executes Viterbi algorithm for embedded HMM. Viterbi algorithm evaluates the likelihood of the best match between the given image observations and the given HMM and performs segmentation of image observations by HMM states. The segmentation is done on the basis of the match found MixSegmL2 Segments observations from all training images by mixture components of newly assigned states void cvmixsegml2( CvImgObsInfo** obsinfoarray, int numimg, CvEHMM* hmm ); obsinfoarray:: Array of pointers to the observation structures.numimg:: Length of the array.hmm:: HMM. The function cvmixsegml2 segments observations from all training images by mixture components of newly Viterbi algorithm-assigned states. The function uses Euclidean distance to group vectors around the existing mixtures centers. CvvImage 类参考手册 1.CvvImage 使用说明和注意事项 由于 CvvImage 是在 highgui.h 头文件中声明的, 因此如果您的程序中需要使用, 则必须在开头包含此头文件 #include <highgui.h> CvvImage 对应 CImage 宏 : #define CImage CvvImage 注意事项 : 292

293 由于 CImage 太常见, 很容易造成冲突, 因此建议不要使用该宏 ( 可以直接删去此宏定义 ) 警告 : 参数中含有 HDC( 注 : 一种 windows 系统下定义的变量类型, 用来描述设备描述表的句柄类型 ) 类型的并不能保证移植到其他平台, 例如 Show/DrawToHDC 等 后文中的 DC, 即 device context( 设备环境 ), 一般可以理解为 windows 操作系统为方便绘图而抽象的 绘图表面, 往窗口上绘图, 有时也被说成是 往窗口 DC 上绘图 2.CvvImage::Create bool CvvImage::Create(int w, int h, int bpp, int origin); 创建一个图像 成功返回 true, 失败返回 false w h bpp origin 图像宽 图像高 每个像素的 bit 数, 值等于像素深度乘以通道数 0 - 顶 左结构, 1 - 底 左结构 (Windows bitmaps 风格 ) 例 : // 创建一个 400 行 600 列的, IPL_DEPTH_8U 类型的 3 通道图像, 顶 左结构 CvvImage img; bool flag = img.create(600, 400, IPL_DEPTH_8U*3, 0); if(!flag) printf(" 创建图像失败!"); 3.CvvImage::CopyOf void CvvImage::CopyOf(CvvImage& img, int desired_color); void CvvImage::CopyOf(IplImage* img, int desired_color); 从 img 复制图像到当前的对象中 img 要复制的图像 desired_color 为复制后图像的通道数, 复制后图像的像素深度为 8bit 293

294 例 : // 读一个图像, 然后复制为 1 个 3 通道的彩色图像 CvvImage img1, img2; img1.load("example.tiff"); img2.copyof(img1, 3); 4.CvvImage::Load bool CvvImage::Load(const char* filename, int desired_color); 装载一个图像 filename 图像文件名称 desired_color 图像波段数, 和 cvloadimage 类似 5.CvvImage::LoadRect bool CvvImage::LoadRect(const char* filename, int desired_color, CvRect rect); 从图像读出一个区域 filename 图像名称 desired_color 图像波段数, 和 cvloadimage 类似 rect 要读取的图像范围 注 LoadRect 是先用 Load 装载一个图像, 然后再将 rect 设置为图像的 ROI 区域, 然后复制图像得到, 因此, 如果一个图像很大 ( 例如几百 MB), 即使想从中只读几个像素也是会失败的 6.CvvImage::Save bool CvvImage::Save(const char* filename); 保存图像 和 cvsaveimage 相似 7.CvvImage::Show void CvvImage::Show(const char* window); 294

295 显示一个图像 和 cvshowimage 相似 8.CvvImage::Show void CvvImage::Show(HDC dc, int x, int y, int w, int h, int from_x, int from_y); 绘制图像的部分到 DC 图像没有缩放 此函数仅在 Windows 下有效 dc 设备描述符 x 局部图像显示在 DC 上, 从 DC 上的第 x 列开始 y 局部图像显示在 DC 上, 从 DC 上的第 y 列开始 (x,y) 为局部图像显示在 DC 上的起始位置 w 局部图像宽度 h 局部图像高度 from_x 从图像的第 from_x 列开始显示 from_y 从图像的第 from_y 行开始显示 例 : // 从图像 10 行 20 列开始, 显示 400 行 600 列图像到设备描述符的 100 行 200 列开始的位置 CvvImage img; img.load("example.tiff"); img.show(hdc, 200, 100, 600, 400, 20, 10); 9.CvvImage::DrawToHDC void CImage::DrawToHDC(HDC hdcdst, RECT* pdstrect); 绘制图像的 ROI 区域到 DC 的 pdstrect, 如果图像大小和 pdstrect 不一致, 图像会拉伸 / 压缩 此函数仅在 Windows 下有效 hdcdst 设备描述符 pdstrect 对应的设备描述符区域 295

296 例 : CvvImage img; img.load("example.tiff"); CRect rect; rect.left = 100; rect.top = 200; rect.right = rect.left + 600; rect.bottom = rect.top + 400; img.drawtohdc(hdc, &rect); 10.CvvImage::Fill void CvvImage::Fill(int color); 以 color 颜色填充图像 11.CvvImage 类定义 /* CvvImage class definition */ class CV_EXPORTS CvvImage public: CvvImage(); virtual ~CvvImage(); /* Create image (BGR or grayscale) */ virtual bool Create( int width, int height, int bits_per_pixel, int image_origin = 0 ); /* Load image from specified file */ virtual bool Load( const char* filename, int desired_color = 1 ); /* Load rectangle from the file */ virtual bool LoadRect( const char* filename, int desired_color, CvRect r ); #ifdef WIN32 virtual bool LoadRect( const char* filename, int desired_color, RECT r ) return LoadRect( filename, desired_color, 296

297 r.bottom - r.top )); } #endif cvrect( r.left, r.top, r.right - r.left, /* Save entire image to specified file. */ virtual bool Save( const char* filename ); /* Get copy of input image ROI */ virtual void CopyOf( CvvImage& image, int desired_color = -1 ); virtual void CopyOf( IplImage* img, int desired_color = -1 ); IplImage* GetImage() return m_img; }; virtual void Destroy(void); /* width and height of ROI */ int Width() return!m_img? 0 :!m_img->roi? m_img->width : m_img->roi->width; }; int Height() return!m_img? 0 :!m_img->roi? m_img->height : m_img->roi->height;}; int Bpp() return m_img? (m_img->depth & 255)*m_img->nChannels : 0; }; virtual void Fill( int color ); /* draw to highgui window */ virtual void Show( const char* window ); #ifdef WIN32 /* draw part of image to the specified DC */ virtual void Show( HDC dc, int x, int y, int width, int height, int from_x = 0, int from_y = 0 ); /* draw the current image ROI to the specified rectangle of the destination DC */ virtual void DrawToHDC( HDC hdcdst, RECT* pdstrect ); #endif protected: }; IplImage* m_img; 297

298 CvImage 类参考手册 CvImage 使用前需要包含 cv.h 头文件 #include <cv.h> 1.CvImage::CvImage bool CvImage::CvImage(); bool CvImage::CvImage(CvSize size, int depth, int channels); bool CvImage::CvImage(IplImage* piplimg); bool CvImage::CvImage(const CvImage& cvimg); bool CvImage::CvImage(const char* filename, const char* imgname=0, int color=-1); bool CvImage::CvImage(CvFileStorage* fs, const char* mapname, const char* imgname); bool CvImage::CvImage(CvFileStorage* fs, const char* seqname, int idx); 默认构造函数, 创建一个图像 影象对应的数据在析构的时候自动被释放 size 图像大小 depth 像素深度 channels 通道数 piplimg IplImage 结构影象 cvimg CvImage 对象的引用 filename 暂无 imgname 暂无 color 暂无 fs 暂无 mapname 暂无 seqname 暂无 idx 暂无 298

299 2.CvImage::~CvImage CvImage::~CvImage(); 析构函数 3.CvImage::clone CvImage CvImage::clone(); 生成当前影象的一个 copy 4.CvImage::create void CvImage::create(CvSize size, int depth, int channels); 创建一个影象 size 图像大小 depth 像素深度 channels 通道数 5.CvImage::release void CvImage::release(); 释放一个打开的影象 6.CvImage::clear void CvImage::clear(); 释放一个打开的影象 和 release 功能相同 7.CvImage::attach void CvImage::attach(IplImage* img, bool use_refcount=true); 将一个影象和当前对象绑定 如果使用引用计数, 则引用计数增加 1 当引用计数减少到 0 的时候释放 img 对应的内存 img 299

300 图像数据 use_refcount 是否使用引用计数 8.CvImage::detach void CvImage::detach(); 取消当前对象绑定的数据 如果使用引用计数, 则引用计数减少 1 当引用计数减少到 0 的时候释放 img 对应的内存 9.CvImage::load bool CvImage::load(const char* filename, const char* imgname=0, int color=-1); 暂无 10.CvImage::read bool CvImage::read(CvFileStorage* fs, const char* mapname, const char* imgname ); bool CvImage::read(CvFileStorage* fs, const char* seqname, int idx); 暂无 11.CvImage::save void CvImage::save(const char* filename, const char* imgname); 暂无 12.CvImage::write void CvImage::write(CvFileStorage* fs, const char* imgname); 暂无 13.CvImage::show void CvImage::show(const char* window_name); 在指定窗口中显示图像 window_name 窗口的名字 14.CvImage::is_valid 300

301 bool CvImage::is_valid(); 当前对象是否已经帮定了有效的影象 是返回 true, 否返回 false 15.CvImage::width int CvImage::width()const; 返回影象的宽度 没有影象的话返回 0 16.CvImage::height int CvImage::height()const; 返回影象的高度 没有影象的话返回 0 17.CvImage::size CvSize CvImage::size()const; 返回影象的尺寸 没有影象的话返回 cvsize(0,0) 18.CvImage::roi_size CvSize CvImage::roi_size()const; 返回影象的 ROI 区域大小 没有影象的话返回 cvsize(0,0) 19.CvImage::roi CvRect CvImage::roi()const; 返回影象的 ROI 区域 没有影象的话返回 cvrect(0,0,0,0) 20.CvImage::coi int CvImage::coi()const; 返回影象的 COI 没有影象的话返回 0 21.CvImage::set_roi void CvImage::set_roi(CvRect roi); 设置影象的 ROI 301

302 roi ROI 区域 22.CvImage::reset_roi void CvImage::reset_roi(); 重置影象的 ROI 23.CvImage::set_coi void CvImage::set_coi(); 返回影象的 COI 24.CvImage::depth int CvImage::depth(); 返回像素的深度 没有影象的话返回 0 25.CvImage::channels int CvImage::channels(); 返回影象通道数 没有影象的话返回 0 26.CvImage::pix_size int CvImage::pix_size(); 返回影象像素的大小 值等于像素深度乘以通道数 27.CvImage::data uchar* CvImage::data(); const uchar* CvImage::data()const; 获取对应对应的影象数据地址 没有影象的话返回 NULL 28.CvImage::step int CvImage::step()const; 返回 IplImage::widthStep, 没有影象的话返回 0 302

303 29.CvImage::origin int CvImage::origin()const; 返回影象结构 0- 顶 左结构,1- 底 左结构 (Windows bitmaps 风格 ) 30.CvImage::roi_row uchar* CvImage::roi_row(int y); const uchar* CvImage::roi_row(int y)const; 返回第 y 行数据 没有影象的话返回 NULL y 影象行数 31. 运算符重载 operator const IplImage* (); operator IplImage* (); CvImage& operator=(const CvImage& img); CvImage 中的陷阱和 BUG 1.CvImage 类的定义 class CV_EXPORTS CvImage public: CvImage() : image(0), refcount(0) } CvImage( CvSize size, int depth, int channels ) image = cvcreateimage( size, depth, channels ); refcount = image? new int(1) : 0; } CvImage( IplImage* img ) : image(img) refcount = image? new int(1) : 0; } 303

304 ~CvImage() if( refcount &&!(--*refcount) ) cvreleaseimage( &image ); delete refcount; } } void attach( IplImage* img, bool use_refcount=true ) if( refcount ) if( --*refcount == 0 ) cvreleaseimage( &image ); delete refcount; } image = img; refcount = use_refcount && image? new int(1) : 0; } void detach() if( refcount ) if( --*refcount == 0 ) cvreleaseimage( &image ); delete refcount; refcount = 0; } image = 0; } CvImage& operator = (const CvImage& img) if( img.refcount ) ++*img.refcount; if( refcount &&!(--*refcount) ) cvreleaseimage( &image ); image=img.image; refcount=img.refcount; return *this; } 304

305 protected: IplImage* image; int* refcount; }; // 实际影象 // 引用计数 CvImage 类的相关代码在以下位置 : OpenCV\cxcore\include\cxcore.hpp OpenCV\cxcore\src\cximage.cpp 这里给出的只是部分函数 为了提高效率,CvImage 采用的是引用计数 不过目前的 CvImage 实现中, 引用计数机制存在 bug 2. 关于引用计数 引用计数应该也可以叫写时复制技术 就是在复制一个数据时, 先只是简单地复制数据的指针 ( 地址 ), 只有在数据被修改的时候才真的进行数据的复制操作 写时复制技术对用户是透明的, 也就是说用户可以当作数据是真的复制了 一般数据 ( 或者是文件, 类等 ) 都会对应创建 / 销毁操作 因此, 采用写时复制技术的数据一般还对应一个计数, 记录该数据被别人引用的次数 数据在第一次被创建的时候被设置为 1, 以后每次被重复创建则增加 1, 如果是被销毁则减少 1 再销毁数据减少引用计数的时候, 如果记录变为 0 则真的执行删除数据操作, 否则的话只执行逻辑删除 这里需要注意的一点是, 每个引用计数和它对应的数据是绑定的 因此, 任何一个引用计数都不应该独立于数据存在 3.CvImage 中的引用计数机制 class CV_EXPORTS CvImage IplImage* image; int* refcount; }; image 指向影像数据的地址,refcount 指向影像数据对应的引用计数的地址 需要强调的一点是, refcount 指向的引用计数并不属于哪个类, 而是属于 image 指向影像数据! 任何将影像数据和其对应的引用计数分离的操作都是错误的 4.CvImage(IplImage* img) 陷阱 305

306 假设有下面一个段代码 : IplImage *piplimg = cvloadimage("load.tiff"); CvImage cvimg(piplimg); } cvsaveimage("save.tiff", piplimg); 虽然逻辑上好像没有错误, 但再执行到 cvsaveimage 语句的时候却会产生异常! 跟踪调试后发现, 原来 piplimg 对应的数据在 cvimg 析构的时候被释放了! 仔细分析后会发现,CvImage 将 piplimg 对应的数据和它本身的 refcount 绑定到一起了 piplimg 对应的数据虽然不属于 CvImage, 但是它却依据 refcount 对其进行管理, 直到 (*refcount) 变为 0 的时候私自释放了 piplimg 影像 对于这个问题, 我不建议使用引用计数, 因此可以将代码修改为 : CvImage( IplImage* img, bool use_refcount=false) : image(img) refcount = use_refcount && image? new int(1) : 0; } 在默认的时候不使用引用计数机制, 用户自己维护 img 内存空间 5.attach 问题 void attach( IplImage* img, bool use_refcount=true ) if( refcount ) if( --*refcount == 0 ) cvreleaseimage( &image ); delete refcount; } image = img; refcount = use_refcount && image? new int(1) : 0; } attach 是将一个 IplImage 影像绑定到 CvImage 其中的一个陷阱和前面的 CvImage 类似 : IplImage *piplimg = cvloadimage("load.tiff"); CvImage cvimg; cvimg.attach(piplimg); } 306

307 cvsaveimage("save.tiff", piplimg); // 异常 处理是方法是把参数 use_refcount 的默认值改为 false 除了和 CvImage 类型的陷阱外,attach 本身还有一个 bug! 前面我们分析过,CvImage 类中 refcount 指向的空间和 image 指向的空间是绑在一起的 因此,if( --*refcount == 0 ) 语句中将 cvreleaseimage( &image ) 和 delete refcount 分离的操作肯定是错误的!! 假设有以下代码 : IplImage *piplimg = cvloadimage("load.tiff"); CvImage cvimg; cvimg.create(cvsize(600,400), 8, 1); // 创建一个 600*400 的单字节单通道影像 CvImage cvimgx(cvimg); 造 cvimgx cvimgx.attach(piplimg); } cvsaveimage("save.tiff", piplimg); // 由 cvimg 拷贝构 代码将在执行完 cvimgx.attach(piplimg) 语句后发生异常! 分析代码可以发现,cvImg.create 先创建了一个影像, 同时影像还对应一个引用计数 由于 cvimgx 是有 cvimg 拷贝构造得到, 因此 cvimgx 也保存了和 cvimg 一样的 image 和 refcount 在接着执行的 attach 中,cvImgX 将 refcount 指向的空间释放 (delete refcount) 注意,cvImgX 和 cvimg 的 refcount 对应同一个空间!! 那么在,cvImg 退出花括号执行析构函数的时候,delete refcount 语句就非法了! 修改 bug 后的 attach 代码 : void attach( IplImage* img, bool use_refcount=false ) // use_refcount 默认值没有修改 if( refcount ) if( --*refcount == 0 ) // 同时释放 } } cvreleaseimage( &image ); delete refcount; 307

308 } image = img; refcount = use_refcount && image? new int(1) : 0; 由于 CvImage 中的许多函数都基于 attach 实现, 因此没有修改 use_refcount 的默认值 detach 中的问题和 attach 相似, 代码修改如下 : void detach() if( refcount ) if( --*refcount == 0 ) // 同时释放 } cvreleaseimage( &image ); delete refcount; } refcount = 0; } image = 0; 6. 重载操作符 = 时的内存泄漏 CvImage& operator = (const CvImage& img) if( img.refcount ) ++*img.refcount; if( refcount &&!(--*refcount) ) cvreleaseimage( &image ); image=img.image; refcount=img.refcount; return *this; } 假设有以下类似代码 : CvImage cvimg1, cvimg2; cvimg1.create(cvsize(600,400), 8, 1); cvimg2.create(cvsize(800,500), 8, 1); cvimg1 = cvimg2; 虽然看着很清晰, 但是该代码却存在内存泄漏! 分析如下 : 308

309 cvimg1 先创建一个 (600,400) 大小的影像, 默认还对应一个引用计数 (refcount 指向的空间 ) cvimg2 也采用同样的方式创建一个类似的影像 注意 :cvimg1 和 cvimg1 中 refcount 指向的空间是不同的!! 下面执行 = 操作时,cvImg1 的 image 空间被释放 (cvreleaseimage( &image )), 但是 cvimg1 的 refcount 指向的空间却没有释放! 然后,cvImg1 的 refcount 指向了 cvimg2 的 refcount 这样,cvImg1 的 refcount 指向内存就丢失了! 修改后的代码 : CvImage& operator = (const CvImage& img) if( img.refcount ) ++*img.refcount; if( refcount &&!(--*refcount) ) cvreleaseimage( &image ); // 释放 refcount } delete refcount; } image=img.image; refcount=img.refcount; return *this; 7. 小节 虽然讲了这么多关于 CvImage 的陷阱和 bug, 单主要目的还是为了更好地使用 CvImage 这里给出一个建议 : 在将 IplImage 数据和 CvImage 进行绑定, 或者是基于 IplImage 数据构造 CvImage 对象的时候, 要清楚是否需要使用 CvImage 的引用计数技术 ( 有哪些好处 / 坏处 ) 特别是 attach 默认是采用引用计数的 ( 没改的理由前面已经说明 ) 8. 附 : 修复的 CvImage class CV_EXPORTS CvImage public: CvImage() : image(0), refcount(0) } 309

310 CvImage( CvSize size, int depth, int channels ) image = cvcreateimage( size, depth, channels ); refcount = image? new int(1) : 0; } // 修改 CvImage( IplImage* img, bool use_refcount=false) : image(img) refcount = use_refcount && image? new int(1) : 0; } ~CvImage() if( refcount &&!(--*refcount) ) cvreleaseimage( &image ); delete refcount; } } // 修改 void attach( IplImage* img, bool use_refcount=false ) // use_refcount 默认值没有修改 if( refcount ) if( --*refcount == 0 ) // 同时释放 } cvreleaseimage( &image ); delete refcount; } } image = img; refcount = use_refcount && image? new int(1) : 0; // 修改 void detach() 310

311 if( refcount ) if( --*refcount == 0 ) // 同时释放 } cvreleaseimage( &image ); delete refcount; } refcount = 0; } image = 0; // 修改 CvImage& operator = (const CvImage& img) if( img.refcount ) ++*img.refcount; if( refcount &&!(--*refcount) ) cvreleaseimage( &image ); // 释放 refcount } delete refcount; } image=img.image; refcount=img.refcount; return *this; protected: IplImage* image; int* refcount; }; // 实际影象 // 引用计数 9. 相关页面 CvImage 中的陷阱和 BUG 311

312 Cv 图像处理 梯度 边缘和角点 Sobel 使用扩展 Sobel 算子计算一阶 二阶 三阶或混合图像差分 void cvsobel( const CvArr* src, CvArr* dst, int xorder, int yorder, int aperture_size=3 ); src 输入图像. dst 输出图像. xorder x 方向上的差分阶数 yorder y 方向上的差分阶数 aperture_size 扩展 Sobel 核的大小, 必须是 1, 3, 5 或 7 除了尺寸为 1, 其它情况下, aperture_size aperture_size 可分离内核将用来计算差分 对 aperture_size=1 的情况, 使用 3x1 或 1x3 内核 ( 不进行高斯平滑操作 ) 这里有一个特殊变量 CV_SCHARR (=-1), 对应 3x3 Scharr 滤波器, 可以给出比 3x3 Sobel 滤波更精确的结果 Scharr 滤波器系数是 : 对 x- 方向或矩阵转置后对 y- 方向 函数 cvsobel 通过对图像用相应的内核进行卷积操作来计算图像差分 : 由于 Sobel 算子结合了 Gaussian 平滑和微分, 所以, 其结果或多或少对噪声有一定的鲁棒性 通常情况, 函数调用采用如下参数 (xorder=1, yorder=0, aperture_size=3) 或 (xorder=0, yorder=1, aperture_size=3) 来计算一阶 x- 或 y- 方向的图像差分 第一种情况对应 : 核 312

313 第二种对应 : 或者 核的选则依赖于图像原点的定义 (origin 来自 IplImage 结构的定义 ) 由于该函数不进行图像尺度变换, 所以和输入图像 ( 数组 ) 相比, 输出图像 ( 数组 ) 的元素通常具有更大的绝对数值 ( 译者注 : 即像素的位深 ) 为防止溢出, 当输入图像是 8 位的, 要求输出图像是 16 位的 当然可以用函数 cvconvertscale 或 cvconvertscaleabs 转换为 8 位的 除了 8- 位图像, 函数也接受 32- 位浮点数图像 所有输入和输出图像都必须是单通道的, 并且具有相同的图像尺寸或者 ROI 尺寸 Laplace 计算图像的 Laplacian 变换 void cvlaplace( const CvArr* src, CvArr* dst, int aperture_size=3 ); src 输入图像. dst 输出图像. aperture_size 核大小 ( 与 cvsobel 中定义一样 ). 函数 cvlaplace 计算输入图像的 Laplacian 变换, 方法是先用 sobel 算子计算二阶 x- 和 y- 差分, 再求和 : 对 aperture_size=1 则给出最快计算结果, 相当于对图像采用如下内核做卷积 : 313

314 类似于 cvsobel 函数, 该函数也不作图像的尺度变换, 所支持的输入 输出图像类型的组合和 cvsobel 一致 Canny 采用 Canny 算法做边缘检测 void cvcanny( const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3 ); image 单通道输入图像. edges 单通道存储边缘的输出图像 threshold1 第一个阈值 threshold2 第二个阈值 aperture_size Sobel 算子内核大小 ( 见 cvsobel). 函数 cvcanny 采用 CANNY 算法发现输入图像的边缘而且在输出图像中标识这些边缘 threshold1 和 threshold2 当中的小阈值用来控制边缘连接, 大的阈值用来控制强边缘的初始分割 注意事项 :cvcanny 只接受单通道图像作为输入 外部链接 : 经典的 canny 自调整阈值算法的一个 opencv 的实现见在 OpenCV 中自适应确定 canny 算法的分割门限 PreCornerDetect 计算用于角点检测的特征图, void cvprecornerdetect( const CvArr* image, CvArr* corners, int aperture_size=3 ); image 输入图像. corners 保存候选角点的特征图 aperture_size Sobel 算子的核大小 ( 见 cvsobel). 314

315 函数 cvprecornerdetect 计算函数其中 表示一阶图像差分, 表示二阶图像差分 角点被认为是函数的局部最大值 : // 假设图像格式为浮点数 IplImage* corners = cvcloneimage(image); IplImage* dilated_corners = cvcloneimage(image); IplImage* corner_mask = cvcreateimage( cvgetsize(image), 8, 1 ); cvprecornerdetect( image, corners, 3 ); cvdilate( corners, dilated_corners, 0, 1 ); cvsubs( corners, dilated_corners, corners ); cvcmps( corners, 0, corner_mask, CV_CMP_GE ); cvreleaseimage( &corners ); cvreleaseimage( &dilated_corners ); CornerEigenValsAndVecs 计算图像块的特征值和特征向量, 用于角点检测 void cvcornereigenvalsandvecs( const CvArr* image, CvArr* eigenvv, int block_size, int aperture_size=3 ); image 输入图像. eigenvv 保存结果的数组 必须比输入图像宽 6 倍 block_size 邻域大小 ( 见讨论 ). aperture_size Sobel 算子的核尺寸 ( 见 cvsobel). 对每个象素, 函数 cvcornereigenvalsandvecs 考虑 block_size block_size 大小的邻域 S(p), 然后在邻域上计算图像差分的相关矩阵 : 然后它计算矩阵的特征值和特征向量, 并且按如下方式 (λ1, λ2, x1, y1, x2, y2) 存储这些值到输出图像中, 其中 λ1, λ2 - M 的特征值, 没有排序 (x1, y1) - 特征向量, 对 λ1 (x2, y2) - 特征向量, 对 λ2 315

316 CornerMinEigenVal 计算梯度矩阵的最小特征值, 用于角点检测 void cvcornermineigenval( const CvArr* image, CvArr* eigenval, int block_size, int aperture_size=3 ); image 输入图像. eigenval 保存最小特征值的图像. 与输入图像大小一致 block_size 邻域大小 ( 见讨论 cvcornereigenvalsandvecs). aperture_size Sobel 算子的核尺寸 ( 见 cvsobel). 当输入图像是浮点数格式时, 该参数表示用来计算差分固定的浮点滤波器的个数. 函数 cvcornermineigenval 与 cvcornereigenvalsandvecs 类似, 但是它仅仅计算和存储每个象素点差分相关矩阵的最小特征值, 即前一个函数的 min(λ1, λ2) CornerHarris 哈里斯 (Harris) 角点检测 void cvcornerharris( const CvArr* image, CvArr* harris_responce, int block_size, int aperture_size=3, double k=0.04 ); image 输入图像 harris_responce 存储哈里斯 (Harris) 检测 responces 的图像 与输入图像等大 block_size 邻域大小 ( 见关于 cvcornereigenvalsandvecs 的讨论 ) aperture_size 扩展 Sobel 核的大小 ( 见 cvsobel) 格式. 当输入图像是浮点数格式时, 该参数表示用来计算差分固定的浮点滤波器的个数 k harris 检测器的自由参数 参见下面的公式 函数 cvcornerharris 对输入图像进行 Harris 边界检测 类似于 cvcornermineigenval 和 cvcornereigenvalsandvecs 对每个像素, 在 block_size*block_size 大小的邻域上, 计算其 2*2 梯度共变矩阵 ( 或相关异变矩阵 )M 然后, 将 det(m) - k*trace(m)2 ( 这里 2 是平方 ) 保存到输出图像中 输入图像中的角点在输出图像中由局部最大值表示 316

317 FindCornerSubPix 精确角点位置 void cvfindcornersubpix( const CvArr* image, CvPoint2D32f* corners, int count, CvSize win, CvSize zero_zone, CvTermCriteria criteria ); image 输入图像. corners 输入角点的初始坐标, 也存储精确的输出坐标 count 角点数目 win 搜索窗口的一半尺寸 如果 win=(5,5) 那么使用 5*2+1 5*2+1 = 大小的搜索窗口 zero_zone 死区的一半尺寸, 死区为不对搜索区的中央位置做求和运算的区域 它是用来避免自相关矩阵出现的某些可能的奇异性 当值为 (-1,-1) 表示没有死区 criteria 求角点的迭代过程的终止条件 即角点位置的确定, 要么迭代数大于某个设定值, 或者是精确度达到某个设定值 criteria 可以是最大迭代数目, 或者是设定的精确度, 也可以是它们的组合 函数 cvfindcornersubpix 通过迭代来发现具有子象素精度的角点位置, 或如图所示的放射鞍点 (radial saddle points) 子象素级角点定位的实现是基于对向量正交性的观测而实现的, 即从中央点 q 到其邻域点 p 的向量和 p 点处的图像梯度正交 ( 服从图像和测量噪声 ) 考虑以下的表达式: εi=dipit (q-pi) 317

318 其中,DIpi 表示在 q 的一个邻域点 pi 处的图像梯度,q 的值通过最小化 εi 得到 通过将 εi 设为 0, 可以建立系统方程如下 : sumi(dipi DIpiT) q - sumi(dipi DIpiT pi) = 0 其中 q 的邻域 ( 搜索窗 ) 中的梯度被累加 调用第一个梯度参数 G 和第二个梯度参数 b, 得到 : q=g-1 b 该算法将搜索窗的中心设为新的中心 q, 然后迭代, 直到找到低于某个阈值点的中心位置 GoodFeaturesToTrack 确定图像的强角点 void cvgoodfeaturestotrack( const CvArr* image, CvArr* eig_image, CvArr* temp_image, CvPoint2D32f* corners, int* corner_count, double quality_level, double min_distance, const CvArr* mask=null ); image 输入图像,8- 位或浮点 32- 比特, 单通道 eig_image 临时浮点 32- 位图像, 尺寸与输入图像一致 temp_image 另外一个临时图像, 格式与尺寸与 eig_image 一致 corners 输出参数, 检测到的角点 corner_count 输出参数, 检测到的角点数目 quality_level 最大最小特征值的乘法因子 定义可接受图像角点的最小质量因子 min_distance 限制因子 得到的角点的最小距离 使用 Euclidian 距离 mask ROI: 感兴趣区域 函数在 ROI 中计算角点, 如果 mask 为 NULL, 则选择整个图像 必须为单通道的灰度图, 大小与输入图像相同 mask 对应的点不为 0, 表示计算该点 函数 cvgoodfeaturestotrack 在图像中寻找具有大特征值的角点 该函数, 首先用 cvcornermineigenval 计算输入图像的每一个象素点的最小特征值, 并将结果存储到变量 eig_image 中 然后进行非最大值抑制 ( 仅保留 3x3 邻域中的局部最大值 ) 下一步将最小特征值小于 quality_level max(eig_image(x,y)) 排除掉 最后, 函数确保所有发现的角 318

319 点之间具有足够的距离,( 最强的角点第一个保留, 然后检查新的角点与已有角点之间的距离大于 min_distance ) 采样 插值和几何变换 InitLineIterator 初始化线段迭代器 int cvinitlineiterator( const CvArr* image, CvPoint pt1, CvPoint pt2, CvLineIterator* line_iterator, int connectivity=8 ); image 带采线段的输入图像. pt1 线段起始点 pt2 线段结束点 line_iterator 指向线段迭代器状态结构的指针 connectivity 被扫描线段的连通数,4 或 8. 函数 cvinitlineiterator 初始化线段迭代器, 并返回两点之间的象素点数目 两个点必须在图像内 当迭代器初始化后, 连接两点的光栅线上所有点, 都可以连续通过调用 CV_NEXT_LINE_POINT 来得到 线段上的点是使用 4- 连通或 8- 连通利用 Bresenham 算法逐点计算的 例子 : 使用线段迭代器计算彩色线上象素值的和 CvScalar sum_line_pixels( IplImage* image, CvPoint pt1, CvPoint pt2 ) CvLineIterator iterator; int blue_sum = 0, green_sum = 0, red_sum = 0; int count = cvinitlineiterator( image, pt1, pt2, &iterator, 8 ); for( int i = 0; i < count; i++ ) blue_sum += iterator.ptr[0]; green_sum += iterator.ptr[1]; red_sum += iterator.ptr[2]; CV_NEXT_LINE_POINT(iterator); 319

320 /* print the pixel coordinates: demonstrates how to calculate the coordinates */ int offset, x, y; /* assume that ROI is not set, otherwise need to take it into account. */ offset = iterator.ptr - (uchar*)(image->imagedata); y = offset/image->widthstep; x = (offset - y*image->widthstep)/(3*sizeof(uchar) /* size of pixel */); printf("(%d,%d)\n", x, y ); } } return cvscalar( blue_sum, green_sum, red_sum ); } SampleLine 将图像上某一光栅线上的像素数据读入缓冲区 int cvsampleline( const CvArr* image, CvPoint pt1, CvPoint pt2, void* buffer, int connectivity=8 ); image 输入图像 pt1 光栅线段的起点 pt2 光栅线段的终点 buffer 存储线段点的缓存区, 必须有足够大小来存储点 max( pt2.x-pt1.x +1, pt2.y-pt1.y +1 ) :8- 连通情况下, 或者 pt2.x-pt1.x + pt2.y-pt1.y +1 : 4 - 连通情况下. connectivity 线段的连通方式, 4 or 8. 函数 cvsampleline 实现了线段迭代器的一个特殊应用 它读取由 pt1 和 pt2 两点确定的线段上的所有图像点, 包括终点, 并存储到缓存中 GetRectSubPix 从图像中提取象素矩形, 使用子象素精度 320

321 void cvgetrectsubpix( const CvArr* src, CvArr* dst, CvPoint2D32f center ); src dst center 输入图像. 提取的矩形. 提取的象素矩形的中心, 浮点数坐标 中心必须位于图像内部. 函数 cvgetrectsubpix 从图像 src 中提取矩形 : dst(x, y) = src(x + center.x - (width(dst)-1)*0.5, y + center.y - (height(dst)-1)*0.5) 其中非整数象素点坐标采用双线性插值提取 对多通道图像, 每个通道独立单独完成提取 尽管函数要求矩形的中心一定要在输入图像之中, 但是有可能出现矩形的一部分超出图像边界的情况, 这时, 该函数复制边界的模识 (hunnish: 即用于矩形相交的图像边界线段的象素来代替矩形超越部分的象素 ) GetQuadrangleSubPix 提取象素四边形, 使用子象素精度 void cvgetquadranglesubpix( const CvArr* src, CvArr* dst, const CvMat* map_matrix ); src 输入图像. dst 提取的四边形. map_matrix 3 2 变换矩阵 [A b] ( 见讨论 ). 函数 cvgetquadranglesubpix 以子象素精度从图像 src 中提取四边形, 使用子象素精度, 并且将结果存储于 dst, 计算公式是 : dst(x + width(dst) / 2,y + height(dst) / 2) = src(a11x + A12y + b1,a21x + A22y + b2) 其中 A 和 b 均来自映射矩阵 ( 译者注 :A, b 为几何形变参数 ), 映射矩阵为 : 321

322 其中在非整数坐标 的象素点值通过双线性变换得到 当函数需要图像 边界外的像素点时, 使用重复边界模式 (replication border mode) 恢复出所需的值 多通道图像的每一个通道都单独计算 例子 : 使用 cvgetquadranglesubpix 进行图像旋转 #include "cv.h" #include "highgui.h" #include "math.h" int main( int argc, char** argv ) IplImage* src; /* the first command line parameter must be image file name */ if( argc==2 && (src = cvloadimage(argv[1], -1))!=0) IplImage* dst = cvcloneimage( src ); int delta = 1; int angle = 0; cvnamedwindow( "src", 1 ); cvshowimage( "src", src ); for(;;) float m[6]; double factor = (cos(angle*cv_pi/180.) + 1.1)*3; CvMat M = cvmat( 2, 3, CV_32F, m ); int w = src->width; int h = src->height; m[0] = (float)(factor*cos(-angle*2*cv_pi/180.)); m[1] = (float)(factor*sin(-angle*2*cv_pi/180.)); m[2] = w*0.5f; m[3] = -m[1]; m[4] = m[0]; m[5] = h*0.5f; cvgetquadranglesubpix( src, dst, &M, 1, cvscalarall(0)); cvnamedwindow( "dst", 1 ); cvshowimage( "dst", dst ); if( cvwaitkey(5) == 27 ) 322

323 break; } angle = (angle + delta) % 360; } } return 0; Resize 图像大小变换 void cvresize( const CvArr* src, CvArr* dst, int interpolation=cv_inter_linear ); src 输入图像. dst 输出图像. interpolation 插值方法 : CV_INTER_NN - 最近邻插值, CV_INTER_LINEAR - 双线性插值 ( 缺省使用 ) CV_INTER_AREA - 使用象素关系重采样 当图像缩小时候, 该方法可以避免波纹出现 当图像放大时, 类似于 CV_INTER_NN 方法.. CV_INTER_CUBIC - 立方插值. 函数 cvresize 将图像 src 改变尺寸得到与 dst 同样大小 若设定 ROI, 函数将按常规支持 ROI. WarpAffine 对图像做仿射变换 void cvwarpaffine( const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=cv_inter_linear+cv_warp_fill_outliers, CvScalar fillval=cvscalarall(0) ); src 输入图像. dst 输出图像. map_matrix 323

324 flags 2 3 变换矩阵 插值方法和以下开关选项的组合 : CV_WARP_FILL_OUTLIERS - 填充所有输出图像的象素 如果部分象素落在输入图像的边界外, 那么它们的值设定为 fillval. CV_WARP_INVERSE_MAP - 指定 map_matrix 是输出图像到输入图像的反变换, 因此可以直接用来做象素插值 否则, 函数从 map_matrix 得到反变换 fillval 用来填充边界外面的值 函数 cvwarpaffine 利用下面指定的矩阵变换输入图像 : 如果没有指定 CV_WARP_INVERSE_MAP,, 否则, 函数与 cvgetquadranglesubpix 类似, 但是不完全相同 cvwarpaffine 要求输入和输出图像具有同样的数据类型, 有更大的资源开销 ( 因此对小图像不太合适 ) 而且输出图像的部分可以保留不变 而 cvgetquadranglesubpix 可以精确地从 8 位图像中提取四边形到浮点数缓存区中, 具有比较小的系统开销, 而且总是全部改变输出图像的内容 要变换稀疏矩阵, 使用 cxcore 中的函数 cvtransform GetAffineTransform 由三对点计算仿射变换 CvMat* cvgetaffinetransform( const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* map_matrix ); src 输入图像的三角形顶点坐标 dst 输出图像的相应的三角形顶点坐标 map_matrix 指向 2 3 输出矩阵的指针 函数 cvgetaffinetransform 计算满足以下关系的仿射变换矩阵 : 324

325 这里,dst(i) = (x'i,y'i),src(i) = (xi,yi),i = DRotationMatrix 计算二维旋转的仿射变换矩阵 CvMat* cv2drotationmatrix( CvPoint2D32f center, double angle, double scale, CvMat* map_matrix ); center 输入图像的旋转中心坐标 angle 旋转角度 ( 度 ) 正值表示逆时针旋转( 坐标原点假设在左上角 ). scale 各项同性的尺度因子 map_matrix 输出 2 3 矩阵的指针函数 cv2drotationmatrix 计算矩阵 : [ α β (1-α)*center.x - β*center.y ] [ -β α β*center.x + (1-α)*center.y ] where α=scale*cos(angle), β=scale*sin(angle) 该变换并不改变原始旋转中心点的坐标, 如果这不是操作目的, 则可以通过调整平移量改变其坐标 ( 译者注 : 通过简单的推导可知, 仿射变换的实现是首先将旋转中心置为坐标原点, 再进行旋转和尺度变换, 最后重新将坐标原点设定为输入图像的左上角, 这里的平移量是 center.x, center.y). WarpPerspective 对图像进行透视变换 void cvwarpperspective( const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=cv_inter_linear+cv_warp_fill_outliers, CvScalar fillval=cvscalarall(0) ); src 输入图像. dst 输出图像. map_matrix 3 3 变换矩阵 325

326 flags 插值方法和以下开关选项的组合 : CV_WARP_FILL_OUTLIERS - 填充所有缩小图像的象素 如果部分象素落在输入图像的边界外, 那么它们的值设定为 fillval. CV_WARP_INVERSE_MAP - 指定 matrix 是输出图像到输入图像的反变换, 因此可以直接用来做象素插值 否则, 函数从 map_matrix 得到反变换 fillval 用来填充边界外面的值 函数 cvwarpperspective 利用下面指定矩阵变换输入图像 : 如果没有指定 CV_WARP_INVERSE_MAP,, 否则, 要变换稀疏矩阵, 使用 cxcore 中的函数 cvtransform WarpPerspectiveQMatrix 用 4 个对应点计算透视变换矩阵 CvMat* cvwarpperspectiveqmatrix( const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* map_matrix ); src 输入图像的四边形的 4 个点坐标 dst 输出图像的对应四边形的 4 个点坐标 map_matrix 输出的 3 3 矩阵函数 cvwarpperspectiveqmatrix 计算透视变换矩阵, 使得 : (tix'i,tiy'i,ti)t=matrix (xi,yi,1)t 其中 dst(i)=(x'i,y'i), src(i)=(xi,yi), i=0..3. GetPerspectiveTransform 326

327 由四对点计算透射变换 CvMat* cvgetperspectivetransform( const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* map_matrix ); #define cvwarpperspectiveqmatrix cvgetperspectivetransform src 输入图像的四边形顶点坐标 dst 输出图像的相应的四边形顶点坐标 map_matrix 指向 3 3 输出矩阵的指针 函数 cvgetperspectivetransform 计算满足以下关系的透射变换矩阵 : Remap 这里,dst(i) = (x'i,y'i),src(i) = (xi,yi),i = 对图像进行普通几何变换 void cvremap( const CvArr* src, CvArr* dst, const CvArr* mapx, const CvArr* mapy, int flags=cv_inter_linear+cv_warp_fill_outliers, CvScalar fillval=cvscalarall(0) ); src dst mapx mapy flags 输入图像. 输出图像. x 坐标的映射 (32fC1 image). y 坐标的映射 (32fC1 image). 插值方法和以下开关选项的组合 : CV_WARP_FILL_OUTLIERS - 填充边界外的像素. 如果输出图像的部分象素落在变换后的边界外, 那么它们的值设定为 fillval fillval 用来填充边界外面的值. 327

328 函数 cvremap 利用下面指定的矩阵变换输入图像 : dst(x,y)<-src(mapx(x,y),mapy(x,y)) 与其它几何变换类似, 可以使用一些插值方法 ( 由用户指定, 译者注 : 同 cvresize) 来计算非整数坐标的像素值 LogPolar 把图像映射到极指数空间 void cvlogpolar( const CvArr* src, CvArr* dst, CvPoint2D32f center, double M, int flags=cv_inter_linear+cv_warp_fill_outliers ); src dst center M flags 输入图像 输出图像 变换的中心, 输出图像在这里最精确 幅度的尺度参数, 见下面公式 插值方法和以下选择标志的结合 CV_WARP_FILL_OUTLIERS - 填充输出图像所有像素, 如果这些点有和外点对应的, 则置零 CV_WARP_INVERSE_MAP - 表示矩阵由输出图像到输入图像的逆变换, 并且因此可以直接用于像素插值 否则, 函数从 map_matrix 中寻找逆变换 fillval 用于填充外点的值 函数 cvlogpolar 用以下变换变换输入图像 : 正变换 (CV_WARP_INVERSE_MAP 未置位 ): dst(phi,rho)<-src(x,y) 逆变换 (CV_WARP_INVERSE_MAP 置位 ): dst(x,y)<-src(phi,rho), 这里, 328

329 rho=m+log(sqrt(x2+y2)) phi=atan(y/x) 此函数模仿人类视网膜中央凹视力, 并且对于目标跟踪等可用于快速尺度和旋转变换不变模板匹配 Example. Log-polar transformation. #include <cv.h> #include <highgui.h> int main(int argc, char** argv) IplImage* src; if(argc == 2 && ((src=cvloadimage(argv[1],1))!= 0 )) IplImage* dst = cvcreateimage( cvsize(256,256), 8, 3 ); IplImage* src2 = cvcreateimage( cvgetsize(src), 8, 3 ); cvlogpolar( src, dst, cvpoint2d32f(src->width/2,src->height/2), 40, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS ); cvlogpolar( dst, src2, cvpoint2d32f(src->width/2,src->height/2), 40, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS+CV_WARP_INVERSE_MAP ); cvnamedwindow( "log-polar", 1 ); cvshowimage( "log-polar", dst ); cvnamedwindow( "inverse log-polar", 1 ); cvshowimage( "inverse log-polar", src2 ); cvwaitkey(); } return 0; } 329

330 And this is what the program displays when opencv/samples/c/fruits.jpg is passed to it 形态学操作 CreateStructuringElementEx 创建结构元素 IplConvKernel* cvcreatestructuringelementex( int cols, int rows, int anchor_x, int anchor_y, int shape, int* values=null ); cols 结构元素的列数目 rows 结构元素的行数目 anchor_x 锚点的相对水平偏移量 anchor_y 锚点的相对垂直偏移量 shape 结构元素的形状, 可以是下列值 : CV_SHAPE_RECT, 长方形元素 ; CV_SHAPE_CROSS, 交错元素 a cross-shaped element; CV_SHAPE_ELLIPSE, 椭圆元素 ; CV_SHAPE_CUSTOM, 用户自定义元素 这种情况下参数 values 定义了 mask, 即象素的那个邻域必须考虑 values 330

331 指向结构元素的指针, 它是一个平面数组, 表示对元素矩阵逐行扫描 ( 非零点表示该点属于结构元 ) 如果指针为空, 则表示平面数组中的所有元素都是非零的, 即结构元是一个长方形 ( 该参数仅仅当 shape 参数是 CV_SHAPE_CUSTOM 时才予以考虑 ) 函数 cv CreateStructuringElementEx 分配和填充结构 IplConvKernel, 它可作为形态操作中的结构元素 ReleaseStructuringElement 删除结构元素 void cvreleasestructuringelement( IplConvKernel** element ); element 被删除的结构元素的指针函数 cvreleasestructuringelement 释放结构 IplConvKernel 如果 *element 为 NULL, 则函数不作用 Erode 使用任意结构元素腐蚀图像 void cverode( const CvArr* src, CvArr* dst, IplConvKernel* element=null, int iterations=1 ); src 输入图像. dst 输出图像. element 用于腐蚀的结构元素 若为 NULL, 则使用 3 3 长方形的结构元素 iterations 腐蚀的次数函数 cverode 对输入图像使用指定的结构元素进行腐蚀, 该结构元素决定每个具有最小值象素点的邻域形状 : dst=erode(src,element): dst(x,y)=min((x',y') in element))src(x+x',y+y') 函数可能是本地操作, 不需另外开辟存储空间的意思 腐蚀可以重复进行 (iterations) 次. 对彩色图像, 每个彩色通道单独处理 Dilate 使用任意结构元素膨胀图像 331

332 void cvdilate( const CvArr* src, CvArr* dst, IplConvKernel* element=null, int iterations=1 ); src 输入图像. dst 输出图像. element 用于膨胀的结构元素 若为 NULL, 则使用 3 3 长方形的结构元素 iterations 膨胀的次数函数 cvdilate 对输入图像使用指定的结构元进行膨胀, 该结构决定每个具有最小值象素点的邻域形状 : dst=dilate(src,element): dst(x,y)=max((x',y') in element))src(x+x',y+y') 函数支持 (in-place) 模式 膨胀可以重复进行 (iterations) 次. 对彩色图像, 每个彩色通道单独处理 MorphologyEx 高级形态学变换 void cvmorphologyex( const CvArr* src, CvArr* dst, CvArr* temp, IplConvKernel* element, int operation, int iterations=1 ); src 输入图像. dst 输出图像. temp 临时图像, 某些情况下需要 element 结构元素 operation 形态操作的类型 : CV_MOP_OPEN - 开运算 CV_MOP_CLOSE - 闭运算 CV_MOP_GRADIENT - 形态梯度 CV_MOP_TOPHAT - " 顶帽 " CV_MOP_BLACKHAT - " 黑帽 " 332

333 iterations 膨胀和腐蚀次数. 函数 cvmorphologyex 在膨胀和腐蚀基本操作的基础上, 完成一些高级的形态变换 : 开运算 dst=open(src,element)=dilate(erode(src,element),element) 闭运算 dst=close(src,element)=erode(dilate(src,element),element) 形态梯度 dst=morph_grad(src,element)=dilate(src,element)-erode(src,element) " 顶帽 " dst=tophat(src,element)=src-open(src,element) " 黑帽 " dst=blackhat(src,element)=close(src,element)-src 临时图像 temp 在形态梯度以及对 顶帽 和 黑帽 操作时的 in-place 模式下需要 滤波器与色彩空间变换 Smooth 各种方法的图像平滑 void cvsmooth( const CvArr* src, CvArr* dst, int smoothtype=cv_gaussian, int param1=3, int param2=0, double param3=0, double param4=0 ); src 输入图像. dst 输出图像. smoothtype 平滑方法 : CV_BLUR_NO_SCALE ( 简单不带尺度变换的模糊 ) - 对每个象素的 param1 param2 领域求和 如果邻域大小是变化的, 可以事先利用函数 cvintegral 计算积分图像 CV_BLUR (simple blur) - 对每个象素 param1 param2 邻域求和并做尺度变换 1/(param1 param2). CV_GAUSSIAN (gaussian blur) - 对图像进行核大小为 param1 param2 的高斯卷积 CV_MEDIAN (median blur) - 对图像进行核大小为 param1 param1 的中值滤波 (i.e. 邻域是方的 ). 333

334 CV_BILATERAL ( 双向滤波 ) - 应用双向 3x3 滤波, 彩色 sigma=param1, 空间 sigma=param2. 关于双向滤波, 可参考 _Filtering.html param1 param2 param3 平滑操作的第一个参数. 平滑操作的第二个参数. 对于简单 / 非尺度变换的高斯模糊的情况, 如果 param2 的值为零, 则表示其被设定为 param1 对应高斯参数的 Gaussian sigma ( 标准差 ). 如果为零, 则标准差由下面的核尺寸计算 : sigma = (n/2-1)* , 其中 n=param1 对应水平核, n=param2 对应垂直核. 对小的卷积核 (3 3 to 7 7) 使用如上公式所示的标准 sigma 速度会快 如果 param3 不为零, 而 param1 和 param2 为零, 则核大小有 sigma 计算 ( 以保证足够精确的操作 ). 函数 cvsmooth 可使用上面任何一种方法平滑图像 每一种方法都有自己的特点以及局限 没有缩放的图像平滑仅支持单通道图像, 并且支持 8 位到 16 位的转换 ( 与 cvsobel 和 cvaplace 相似 ) 和 32 位浮点数到 32 位浮点数的变换格式 简单模糊和高斯模糊支持 1- 或 3- 通道, 8- 比特和 32- 比特浮点图像 这两种方法可以 (in-place) 方式处理图像 中值和双向滤波工作于 1- 或 3- 通道, 8- 位图像, 但是不能以 in-place 方式处理图像. 中值滤波中值滤波法是一种非线性平滑技术, 它将每一象素点的灰度值设置为该点某邻域窗口内的所有象素点灰度值的中值 实现方法 : 1. 通过从图像中的某个采样窗口取出奇数个数据进行排序 2. 用排序后的中值取代要处理的数据即可 中值滤波法对消除椒盐噪音非常有效, 在光学测量条纹图象的相位分析处理方法中有特殊作用, 但在条纹中心分析方法中作用不大 中值滤波在图像处理中, 常用于用来保护边缘信息, 是经典的平滑噪声的方法中值滤波原理中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术, 中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个拎域中各点值的中值代替, 让周围的像素值接近的值, 从而消除孤立的噪声点 方法是去某种结构的二维滑动模板, 将板内像素按照像素值的大小进行排序, 生成单调上升 ( 或下降 ) 的为二维数据序列 二维中值滤波输出为 g(x,y)=medf(x-k,y-l),(k,l W)}, 其中,f(x,y),g(x,y) 分别为原始图像和处 334

335 理后图像 W 为二维模板, 通常为 2*2,3*3 区域, 也可以是不同的的形状, 如线状, 圆形, 十字形, 圆环形等 高斯滤波高斯滤波实质上是一种信号的滤波器, 其用途是信号的平滑处理, 我们知道数字图像用于后期应用, 其噪声是最大的问题, 由于误差会累计传递等原因, 很多图像处理教材会在很早的时候介绍 Gauss 滤波器, 用于得到信噪比 SNR 较高的图像 ( 反应真实信号 ) 于此相关的有 Gauss-Lapplace 变换, 其实就是为了得到较好的图像边缘, 先对图像做 Gauss 平滑滤波, 剔除噪声, 然后求二阶导矢, 用二阶导的过零点确定边缘, 在计算时也是频域乘积 => 空域卷积 滤波器就是建立的一个数学模型, 通过这个模型来将图像数据进行能量转化, 能量低的就排除掉, 噪声就是属于低能量部分其实编程运算的话就是一个模板运算, 拿图像的八连通区域来说, 中间点的像素值就等于八连通区的像素值的均值, 这样达到平滑的效果若使用理想滤波器, 会在图像中产生振铃现象 采用高斯滤波器的话, 系统函数是平滑的, 避免了振铃现象 Filter2D 对图像做卷积 void cvfilter2d( const CvArr* src, CvArr* dst, const CvMat* kernel, CvPoint anchor=cvpoint(-1,-1)); src dst kernel anchor 输入图像. 输出图像. 卷积核, 单通道浮点矩阵. 如果想要应用不同的核于不同的通道, 先用 cvsplit 函 数分解图像到单个色彩通道上, 然后单独处理 核的锚点表示一个被滤波的点在核内的位置 锚点应该处于核内部 缺省值 (-1,-1) 表示锚点在核中心 函数 cvfilter2d 对图像进行线性滤波, 支持 In-place 操作 当核运算部分超出输入图像时, 函数从最近邻的图像内部象素插值得到边界外面的象素值 CopyMakeBorder 复制图像并且制作边界 void cvcopymakeborder( const CvArr* src, CvArr* dst, CvPoint offset, 335

336 int bordertype, CvScalar value=cvscalarall(0) ); src 输入图像 dst 输出图像 offset 输入图像 ( 或者其 ROI) 欲拷贝到的输出图像长方形的左上角坐标 ( 或者左下角坐标, 如果以左下角为原点 ) 长方形的尺寸要和原图像的尺寸的 ROI 分之一匹配 bordertype 已拷贝的原图像长方形的边界的类型 : IPL_BORDER_CONSTANT - 填充边界为固定值, 值由函数最后一个参数指定 IPL_BORDER_REPLICATE - 边界用上下行或者左右列来复制填充 ( 其他两种 IPL 边界类型, IPL_BORDER_REFLECT 和 IPL_BORDER_WRAP 现已不支持 ) value 如果边界类型为 IPL_BORDER_CONSTANT 的话, 那么此为边界像素的值 函数 cvcopymakeborder 拷贝输入 2 维阵列到输出阵列的内部并且在拷贝区域的周围制作一个指定类型的边界 函数可以用来模拟和嵌入在指定算法实现中的边界不同的类型 例如 : 和 opencv 中大多数其他滤波函数一样, 一些形态学函数内部使用复制边界类型, 但是用户可能需要零边界或者填充为 1 或 255 的边界 Integral 计算积分图像 void cvintegral( const CvArr* image, CvArr* sum, CvArr* sqsum=null, CvArr* tilted_sum=null ); image 输入图像, W H, 单通道,8 位或浮点 (32f 或 64f). sum 积分图像, W+1 H+1( 译者注 : 原文的公式应该写成 (W+1) (H+1), 避免误会 ), 单通道,32 位整数或 double 精度的浮点数 (64f). sqsum 对象素值平方的积分图像,W+1 H+1( 译者注 : 原文的公式应该写成 (W+1) (H+1), 避免误会 ), 单通道,32 位整数或 double 精度的浮点数 (64f). tilted_sum 旋转 45 度的积分图像, 单通道,32 位整数或 double 精度的浮点数 (64f). 函数 cvintegral 计算一次或高次积分图像 : sum(x,y) = image(x,y) x < X,y < Y 336

337 sqsum(x,y) = image(x,y) 2 x < X,y < Y tilted_sum(x,y) = image(x,y) y < Y, x X < y 利用积分图像, 可以计算在某象素的上 - 右方的或者旋转的矩形区域中进行求和 求均值以及标准方差的计算, 并且保证运算的复杂度为 O(1) 例如: 因此可以在变化的窗口内做快速平滑或窗口相关等操作 CvtColor 色彩空间转换 void cvcvtcolor( const CvArr* src, CvArr* dst, int code ); src dst code 输入的 8-bit, 16-bit 或 32-bit 单倍精度浮点数影像. 输出的 8-bit, 16-bit 或 32-bit 单倍精度浮点数影像. 色彩空间转换, 通过定义 CV_<src_color_space>2<dst_color_space> 常数 ( 见下面 ). 函数 cvcvtcolor 将输入图像从一个色彩空间转换为另外一个色彩空间 函数忽略 IplImage 头中定义的 colormodel 和 channelseq 域, 所以输入图像的色彩空间应该正确指定 ( 包括通道的顺序, 对 RGB 空间而言,BGR 意味着布局为 B0 G0 R0 B1 G1 R1... 层叠的 24- 位格式, 而 RGB 意味着布局为 R0 G0 B0 R1 G1 B1... 层叠的 24- 位格式. 函数做如下变换 : RGB 空间内部的变换, 如增加 / 删除 alpha 通道, 反相通道顺序, 到 16 位 RGB 彩色或者 15 位 RGB 彩色的正逆转换 (Rx5:Gx6:Rx5), 以及到灰度图像的正逆转换, 使用 : RGB[A]->Gray: Y= *R *G *B + 0*A Gray->RGB[A]: R=Y G=Y B=Y A=0 所有可能的图像色彩空间的相互变换公式列举如下 : RGB<=>XYZ (CV_BGR2XYZ, CV_RGB2XYZ, CV_XYZ2BGR, CV_XYZ2RGB): X R Y = * G Z B 337

338 R X G = * Y B Z RGB<=>YCrCb (CV_BGR2YCrCb, CV_RGB2YCrCb, CV_YCrCb2BGR, CV_YCrCb2RGB) Y=0.299*R *G *B Cr=(R-Y)* Cb=(B-Y)* R=Y *(Cr - 128) G=Y *(Cr - 128) *(Cb - 128) B=Y *(Cb - 128) RGB=>HSV (CV_BGR2HSV,CV_RGB2HSV) V=max(R,G,B) S=(V-min(R,G,B))*255/V if V!=0, 0 otherwise (G - B)*60/S, if V=R H= 180+(B - R)*60/S, if V=G 240+(R - G)*60/S, if V=B if H<0 then H=H+360 使用上面从 0 到 360 变化的公式计算色调 (hue) 值, 确保它们被 2 除后能适用于 8 位 RGB=>Lab (CV_BGR2Lab, CV_RGB2Lab) X R/255 Y = * G/255 Z B/255 L = 116*Y1/3 L = 903.3*Y for Y> for Y<= a = 500*(f(X)-f(Y)) b = 200*(f(Y)-f(Z)) where f(t)=t1/3 f(t)=7.787*t+16/116 for t> for t<=

339 上面的公式可以参考 RGB=>HLS (CV_BGR2HLS, CV_RGB2HLS) HSL 表示 hue( 色相 ) saturation( 饱和度 ) lightness( 亮度 ) 有的地方也称为 HSI, 其中 I 表示 intensity( 强度 ) 转换公式见 Bayer=>RGB (CV_BayerBG2BGR, CV_BayerGB2BGR, CV_BayerRG2BGR, CV_BayerGR2BGR, CV_BayerBG2RGB, CV_BayerRG2BGR, CV_BayerGB2RGB, CV_BayerGR2BGR, CV_BayerRG2RGB, CV_BayerBG2BGR, CV_BayerGR2RGB, CV_BayerGB2BGR) Bayer 模式被广泛应用于 CCD 和 CMOS 摄像头. 它允许从一个单独平面中得到彩色图像, 该平面中的 R/G/B 象素点被安排如下 : R G R G R G B G B G R G R G R G B G B G R G R G R G B G B G 对像素输出的 RGB 份量由该像素的 1 2 或者 4 邻域中具有相同颜色的点插值得到 以上的模式可以通过向左或者向上平移一个像素点来作一些修改 转换常量 CV_BayerC1C22RGB RGB} 中的两个字母 C1 和 C2 表示特定的模式类型 : 颜色份量分别来自于第二行, 第二和第三列 比如说, 上述的模式具有很流行的 "BG" 类型 Threshold 对数组元素进行固定阈值操作 void cvthreshold( const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type ); src dst 原始数组 ( 单通道, 8-bit of 32-bit 浮点数 ). 输出数组, 必须与 src 的类型一致, 或者为 8-bit. 339

340 threshold 阈值 max_value 使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值. threshold_type 阈值类型 ( 见讨论 ) 函数 cvthreshold 对单通道数组应用固定阈值操作 该函数的典型应用是对灰度图像进行阈值操作得到二值图像 (cvcmps 也可以达到此目的 ) 或者是去掉噪声, 例如过滤很小或很大象素值的图像点 本函数支持的对图像取阈值的方法由 threshold_type 确定 : threshold_type=cv_thresh_binary: dst(x,y) = max_value, if src(x,y)>threshold 0, otherwise threshold_type=cv_thresh_binary_inv: dst(x,y) = 0, if src(x,y)>threshold max_value, otherwise threshold_type=cv_thresh_trunc: dst(x,y) = threshold, if src(x,y)>threshold src(x,y), otherwise threshold_type=cv_thresh_tozero: dst(x,y) = src(x,y), if (x,y)>threshold 0, otherwise threshold_type=cv_thresh_tozero_inv: dst(x,y) = 0, if src(x,y)>threshold src(x,y), otherwise 下面是图形化的阈值描述 : 340

341 AdaptiveThreshold 自适应阈值方法 void cvadaptivethreshold( const CvArr* src, CvArr* dst, double max_value, int adaptive_method=cv_adaptive_thresh_mean_c, int threshold_type=cv_thresh_binary, int block_size=3, double param1=5 ); src 输入图像. dst 输出图像. max_value 使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值. adaptive_method 自适应阈值算法使用 :CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C ( 见讨论 ). 341

342 threshold_type 取阈值类型 : 必须是下者之一 CV_THRESH_BINARY, CV_THRESH_BINARY_INV block_size 用来计算阈值的象素邻域大小 : 3, 5, 7,... param1 与方法有关的参数 对方法 CV_ADAPTIVE_THRESH_MEAN_C 和 CV_ADAPTIVE_THRESH_GAUSSIAN_C, 它是一个从均值或加权均值提取的常数 ( 见讨论 ), 尽管它可以是负数 函数 cvadaptivethreshold 将灰度图像变换到二值图像, 采用下面公式 : threshold_type=cv_thresh_binary: dst(x,y) = max_value, if src(x,y)>t(x,y) 0, otherwise threshold_type=cv_thresh_binary_inv: dst(x,y) = 0, if src(x,y)>t(x,y) max_value, otherwise 其中 TI 是为每一个象素点单独计算的阈值对方法 CV_ADAPTIVE_THRESH_MEAN_C, 先求出块中的均值, 再减掉 param1 对方法 CV_ADAPTIVE_THRESH_GAUSSIAN_C, 先求出块中的加权和 (gaussian), 再减掉 param1 金字塔及其应用 PyrDown 图像的下采样 void cvpyrdown( const CvArr* src, CvArr* dst, int filter=cv_gaussian_5x5 ); src 输入图像. dst 输出图像, 宽度和高度应是输入图像的一半, 传入前必须已经完成初始化 filter 卷积滤波器的类型, 目前仅支持 CV_GAUSSIAN_5x5 342

343 函数 cvpyrdown 使用 Gaussian 金字塔分解对输入图像向下采样 首先它对输入图像用指定滤波器进行卷积, 然后通过拒绝偶数的行与列来下采样图像 PyrUp 图像的上采样 void cvpyrup( const CvArr* src, CvArr* dst, int filter=cv_gaussian_5x5 ); src dst filter 输入图像. 输出图像, 宽度和高度应是输入图像的 2 倍 卷积滤波器的类型, 目前仅支持 CV_GAUSSIAN_5x5 函数 cvpyrup 使用 Gaussian 金字塔分解对输入图像向上采样 首先通过在图像中插入 0 偶数行和偶数列, 然后对得到的图像用指定的滤波器进行高斯卷积, 其中滤波器乘以 4 做插值 所以输出图像是输入图像的 4 倍大小 (hunnish: 原理不清楚, 尚待探讨 ) 连接部件 CvConnectedComp 连接部件 typedef struct CvConnectedComp double area; /* 连通域的面积 */ float value; /* 分割域的灰度缩放值 */ CvRect rect; /* 分割域的 ROI */ } CvConnectedComp; FloodFill 用指定颜色填充一个连接域 void cvfloodfill( CvArr* image, CvPoint seed_point, CvScalar new_val, CvScalar lo_diff=cvscalarall(0), CvScalar up_diff=cvscalarall(0), CvConnectedComp* comp=null, int flags=4, CvArr* mask=null ); #define CV_FLOODFILL_FIXED_RANGE (1 << 16) #define CV_FLOODFILL_MASK_ONLY (1 << 17) 343

344 image 输入的 1- 或 3- 通道, 8- 比特或浮点数图像 输入的图像将被函数的操作所改变, 除非你选择 CV_FLOODFILL_MASK_ONLY 选项 ( 见下面 ). seed_point 开始的种子点. new_val 新的重新绘制的象素值 lo_diff 当前观察象素值与其部件领域象素或者待加入该部件的种子象素之负差 (Lower difference) 的最大值 对 8- 比特彩色图像, 它是一个 packed value. up_diff 当前观察象素值与其部件领域象素或者待加入该部件的种子象素之正差 (upper difference) 的最大值 对 8- 比特彩色图像, 它是一个 packed value. comp 指向部件结构体的指针, 该结构体的内容由函数用重绘区域的信息填充 flags 操作选项. 低位比特包含连通值, 4 ( 缺省 ) 或 8, 在函数执行连通过程中确定使用哪种邻域方式 高位比特可以是 0 或下面的开关选项的组合 : CV_FLOODFILL_FIXED_RANGE - 如果设置, 则考虑当前象素与种子象素之间的差, 否则考虑当前象素与其相邻象素的差 ( 范围是浮点数 ). CV_FLOODFILL_MASK_ONLY - 如果设置, 函数不填充原始图像 ( 忽略 new_val), 但填充掩模图像 ( 这种情况下 MASK 必须是非空的 ). mask 运算掩模, 应该是单通道 8- 比特图像, 长和宽上都比输入图像 image 大两个象素点 若非空, 则函数使用且更新掩模, 所以使用者需对 mask 内容的初始化负责 填充不会经过 MASK 的非零象素, 例如, 一个边缘检测子的输出可以用来作为 MASK 来阻止填充边缘 或者有可能在多次的函数调用中使用同一个 MASK, 以保证填充的区域不会重叠 注意 : 因为 MASK 比欲填充图像大, 所以 mask 中与输入图像 (x,y) 像素点相对应的点具有 (x+1,y+1) 坐标 函数 cvfloodfill 用指定颜色, 从种子点开始填充一个连通域 连通性由象素值的接近程度来衡量 在点 (x, y) 的象素被认为是属于重新绘制的区域, 如果 : src(x',y')-lo_diff<=src(x,y)<=src(x',y')+up_diff, 灰度图像, 浮动范围 src(seed.x,seed.y)-lo<=src(x,y)<=src(seed.x,seed.y)+up_diff, 灰度图像, 固定范围 src(x',y')r-lo_diffr<=src(x,y)r<=src(x',y')r+up_diffr 和 src(x',y')g-lo_diffg<=src(x,y)g<=src(x',y')g+up_diffg 和 src(x',y')b-lo_diffb<=src(x,y)b<=src(x',y')b+up_diffb, 彩色图像, 浮动范围 src(seed.x,seed.y)r-lo_diffr<=src(x,y)r<=src(seed.x,seed.y)r+up_diffr 和 src(seed.x,seed.y)g-lo_diffg<=src(x,y)g<=src(seed.x,seed.y)g+up_diffg 和 src(seed.x,seed.y)b-lo_diffb<=src(x,y)b<=src(seed.x,seed.y)b+up_diffb, 彩色图像, 固定范围 344

345 其中 src(x',y') 是象素邻域点的值 也就是说, 为了被加入到连通域中, 一个象素的彩色 / 亮度应该足够接近于 : 它的邻域象素的彩色 / 亮度值, 当该邻域点已经被认为属于浮动范围情况下的连通域 固定范围情况下的种子点的彩色 / 亮度值 FindContours 在二值图像中寻找轮廓 int cvfindcontours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour, int header_size=sizeof(cvcontour), int mode=cv_retr_list, int method=cv_chain_approx_simple, CvPoint offset=cvpoint(0,0) ); image 输入的 8- 比特 单通道图像. 非零元素被当成 1, 0 象素值保留为 0 - 从而图像被看成二值的 为了从灰度图像中得到这样的二值图像, 可以使用 cvthreshold, cvadaptivethreshold 或 cvcanny. 本函数改变输入图像内容 storage 得到的轮廓的存储容器 first_contour 输出参数 : 包含第一个输出轮廓的指针 header_size 如果 method=cv_chain_code, 则序列头的大小 >=sizeof(cvchain), 否则 >=sizeof(cvcontour). mode 提取模式. CV_RETR_EXTERNAL - 只提取最外层的轮廓 CV_RETR_LIST - 提取所有轮廓, 并且放置在 list 中 CV_RETR_CCOMP - 提取所有轮廓, 并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界, 次层为洞的内层边界 CV_RETR_TREE - 提取所有轮廓, 并且重构嵌套轮廓的全部 hierarchy method 逼近方法 ( 对所有节点, 不包括使用内部逼近的 CV_RETR_RUNS). CV_CHAIN_CODE - Freeman 链码的输出轮廓. 其它方法输出多边形 ( 定点序列 ). CV_CHAIN_APPROX_NONE - 将所有点由链码形式翻译 ( 转化 ) 为点序列形式 345

346 CV_CHAIN_APPROX_SIMPLE - 压缩水平 垂直和对角分割, 即函数只保留末端的象素点 ; CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS - 应用 Teh-Chin 链逼近算法. CV_LINK_RUNS - 通过连接为 1 的水平碎片使用完全不同的轮廓提取算法 仅有 CV_RETR_LIST 提取模式可以在本方法中应用. offset 每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候, 使用偏移量有用, 因为可以从整个图像上下文来对轮廓做分析. 函数 cvfindcontours 从二值图像中提取轮廓, 并且返回提取轮廓的数目 指针 first_contour 的内容由函数填写 它包含第一个最外层轮廓的指针, 如果指针为 NULL, 则没有检测到轮廓 ( 比如图像是全黑的 ) 其它轮廓可以从 first_contour 利用 h_next 和 v_next 链接访问到 在 cvdrawcontours 的样例显示如何使用轮廓来进行连通域的检测 轮廓也可以用来做形状分析和对象识别 - 见 CVPR2001 教程中的 squares 样例 该教程可以在 SourceForge 网站上找到 StartFindContours 初始化轮廓的扫描过程 CvContourScanner cvstartfindcontours( CvArr* image, CvMemStorage* storage, int header_size=sizeof(cvcontour), int mode=cv_retr_list, int method=cv_chain_approx_simple, CvPoint offset=cvpoint(0,0) ); image 输入的 8- 比特 单通道二值图像 storage 提取到的轮廓容器 header_size 序列头的尺寸 >=sizeof(cvchain) 若 method=cv_chain_code, 否则尺寸 >=sizeof(cvcontour). mode 提取模式, 见 cvfindcontours. method 逼近方法 它与 cvfindcontours 里的定义一样, 但是 CV_LINK_RUNS 不能使用 offset ROI 偏移量, 见 cvfindcontours. 346

347 函数 cvstartfindcontours 初始化并且返回轮廓扫描器的指针 扫描器在 cvfindnextcontour 使用以提取其馀的轮廓 FindNextContour Finds next contour in the image CvSeq* cvfindnextcontour( CvContourScanner scanner ); scanner 被函数 cvstartfindcontours 初始化的轮廓扫描器. 函数 cvfindnextcontour 确定和提取图像的下一个轮廓, 并且返回它的指针 若没有更多的轮廓, 则函数返回 NULL. SubstituteContour 替换提取的轮廓 void cvsubstitutecontour( CvContourScanner scanner, CvSeq* new_contour ); scanner 被函数 cvstartfindcontours 初始化的轮廓扫描器.. new_contour 替换的轮廓函数 cvsubstitutecontour 把用户自定义的轮廓替换前一次的函数 cvfindnextcontour 调用所提取的轮廓, 该轮廓以用户定义的模式存储在边缘扫描状态之中 轮廓, 根据提取状态, 被插入到生成的结构,List, 二层 hierarchy, 或 tree 中 如果参数 new_contour=null, 则提取的轮廓不被包含入生成结构中, 它的所有后代以后也不会被加入到接口中 EndFindContours 结束扫描过程 CvSeq* cvendfindcontours( CvContourScanner* scanner ); scanner 轮廓扫描的指针. 函数 cvendfindcontours 结束扫描过程, 并且返回最高层的第一个轮廓的指针 PyrSegmentation 用金字塔实现图像分割 347

348 void cvpyrsegmentation( IplImage* src, IplImage* dst, CvMemStorage* storage, CvSeq** comp, int level, double threshold1, double threshold2 ); src 输入图像. dst 输出图像. storage Storage: 存储连通部件的序列结果 comp 分割部件的输出序列指针 components. level 建立金字塔的最大层数 threshold1 建立连接的错误阈值 threshold2 分割簇的错误阈值函数 cvpyrsegmentation 实现了金字塔方法的图像分割 金字塔建立到 level 指定的最大层数 如果 p(c(a),c(b))<threshold1, 则在层 i 的象素点 a 和它的相邻层的父亲象素 b 之间的连接被建立起来, 定义好连接部件后, 它们被加入到某些簇中 如果 p(c(a),c(b))<threshold2, 则任何两个分割 A 和 B 属于同一簇 如果输入图像只有一个通道, 那么 p(c¹,c²)= c¹-c². 如果输入图像有单个通道 ( 红 绿 兰 ), 那幺 p(c¹,c²)=0,3 (c¹r-c²r)+0,59 (c¹g-c²g)+0,11 (c¹b-c²b). 每一个簇可以有多个连接部件 图像 src 和 dst 应该是 8- 比特 单通道或 3- 通道图像, 且大小一样 PyrMeanShiftFiltering Does meanshift image segmentation void cvpyrmeanshiftfiltering( const CvArr* src, CvArr* dst, double sp, double sr, int max_level=1, 348

349 CvTermCriteria termcrit=cvtermcriteria(cv_termcrit_iter+cv_termcrit_eps,5,1)); src 输入的 8- 比特,3- 信道图象. dst 和源图象相同大小, 相同格式的输出图象. sp The spatial window radius. 空间窗的半径 sr The color window radius. 色彩窗的半径 max_level Maximum level of the pyramid for the segmentation. termcrit Termination criteria: when to stop meanshift iterations. The function cvpyrmeanshiftfiltering implements the filtering stage of meanshift segmentation, that is, the output of the function is the filtered "posterized" image with color gradients and fine-grain texture flattened. At every pixel (X,Y) of the input image (or down-sized input image, see below) the function executes meanshift iterations, that is, the pixel (X,Y) neighborhood in the joint space-color hyperspace is considered: (x,y): X-sp x X+sp && Y-sp y Y+sp && (R,G,B)-(r,g,b) sr},where (R,G,B) and (r,g,b) are the vectors of color components at (X,Y) and (x,y), respectively (though, the algorithm does not depend on the color space used, so any 3-component color space can be used instead). Over the neighborhood the average spatial value (X',Y') and average color vector (R',G',B') are found and they act as the neighborhood center on the next iteration: (X,Y)~(X',Y'), (R,G,B)~(R',G',B').After the iterations over, the color components of the initial pixel (that is, the pixel from where the iterations started) are set to the final value (average color at the last iteration): I(X,Y) <- (R*,G*,B*).Then max_level>0, the gaussian pyramid of max_level+1 levels is built, and the above procedure is run on the smallest layer. After that, the results are propagated to the larger layer and the iterations are run again only on those pixels where the layer colors differ much (>sr) from the lower-resolution layer, that is, the boundaries of the color regions are clarified. Note, that the results will be actually different from the ones obtained by running the meanshift procedure on the whole original image (i.e. when max_level==0). Watershed 做分水岭图像分割 349

350 void cvwatershed( const CvArr* image, CvArr* markers ); image 输入 8 比特 3 通道图像 markers 输入或输出的 32 比特单通道标记图像 函数 cvwatershed 实现在 [Meyer92] 描述的变量分水岭, 基于非参数标记的分割算法中的一种 在把图像传给函数之前, 用户需要用正指标大致勾画出图像标记的感兴趣区域 比如, 每一个区域都表示成一个或者多个像素值 1,2,3 的互联部分 这些部分将作为将来图像区域的种子 标记中所有的其他像素, 他们和勾画出的区域关系不明并且应由算法定义, 应当被置 0 这个函数的输出则是标记区域所有像素被置为某个种子部分的值, 或者在区域边界则置 -1 注 : 每两个相邻区域也不是必须有一个分水岭边界 (-1 像素 ) 分开, 例如在初始标记图像里有这样相切的部分 opencv 例程文件夹里面有函数的视觉效果演示和用户例程 见 watershed.cpp 图像与轮廓矩 Moments 计算多边形和光栅形状的最高达三阶的所有矩 void cvmoments( const CvArr* arr, CvMoments* moments, int binary=0 ); arr 图像 (1- 通道或 3- 通道, 有 COI 设置 ) 或多边形 ( 点的 CvSeq 或一族点的向量 ). moments 返回的矩状态接口的指针 binary ( 仅对图像 ) 如果标识为非零, 则所有零象素点被当成零, 其它的被看成 1. 函数 cvmoments 计算最高达三阶的空间和中心矩, 并且将结果存在结构 moments 中 矩用来计算形状的重心, 面积, 主轴和其它的形状特征, 如 7 Hu 不变量等 GetSpatialMoment 从矩状态结构中提取空间矩 double cvgetspatialmoment( CvMoments* moments, int x_order, int y_order ); moments 矩状态, 由 cvmoments 计算 x_order 350

351 提取的 x 次矩, x_order >= 0. y_order 提取的 y 次矩, y_order >= 0 并且 x_order + y_order <= 3. 函数 cvgetspatialmoment 提取空间矩, 当图像矩被定义为 : Mx_order,y_order=sumx,y(I(x,y) xx_order yy_order) 其中 I(x,y) 是象素点 (x, y) 的亮度值. GetCentralMoment 从矩状态结构中提取中心矩 double cvgetcentralmoment( CvMoments* moments, int x_order, int y_order ); moments 矩状态结构指针 x_order 提取的 x 阶矩, x_order >= 0. y_order 提取的 y 阶矩, y_order >= 0 且 x_order + y_order <= 3. 函数 cvgetcentralmoment 提取中心矩, 其中图像矩的定义是 : μx_order,y_order=sumx,y(i(x,y) (x-xc)x_order (y-yc)y_order), 其中 xc=m10/m00, yc=m01/m00 - 重心坐标 GetNormalizedCentralMoment 从矩状态结构中提取归一化的中心矩 double cvgetnormalizedcentralmoment( CvMoments* moments, int x_order, int y_order ); moments 矩状态结构指针 x_order 提取的 x 阶矩, x_order >= 0. y_order 提取的 y 阶矩, y_order >= 0 且 x_order + y_order <= 3. 函数 cvgetnormalizedcentralmoment 提取归一化中心矩 : 351

352 ηx_order,y_order= μx_order,y_order/m00((y_order+x_order)/2+1) GetHuMoments 计算 7 Hu 不变量 void cvgethumoments( CvMoments* moments, CvHuMoments* hu_moments ); moments 矩状态结构的指针 hu_moments Hu 矩结构的指针. 函数 cvgethumoments 计算 7 个 Hu 不变量, 它们的定义是 : h1=η20+η02 h2=(η20-η02)²+4η11² h3=(η30-3η12)²+ (3η21-η03)² h4=(η30+η12)²+ (η21+η03)² h5=(η30-3η12)(η30+η12)[(η30+η12)²-3(η21+η03)²]+(3η21-η 03)(η21+η03)[3(η30+η12)²-(η21+η03)²] h6=(η20-η02)[(η30+η12)²- (η21+η03)²]+4η11(η30+η12)(η21+η 03) h7=(3η21-η03)(η21+η03)[3(η30+η12)²-(η21+η03)²]-(η30-3η 12)(η21+η03)[3(η30+η12)²-(η21+η03)²] 这些值被证明为对图像缩放 旋转和反射的不变量 对反射, 第 7 个除外, 因为它的符号会因为反射而改变 特殊图像变换 HoughLines 利用 Hough 变换在二值图像中找到直线 CvSeq* cvhoughlines2( CvArr* image, void* line_storage, int method, double rho, double theta, int threshold, double param1=0, double param2=0 ); image 输入 8- 比特 单通道 ( 二值 ) 图像, 当用 CV_HOUGH_PROBABILISTIC 方法检测的时候其内容会被函数改变 line_storage 352

353 method 检测到的线段存储仓. 可以是内存存储仓 ( 此种情况下, 一个线段序列在存储仓中被创建, 并且由函数返回 ), 或者是包含线段参数的特殊类型 ( 见下面 ) 的具有单行 / 单列的矩阵 (CvMat*) 矩阵头为函数所修改, 使得它的 cols/rows 将包含一组检测到的线段 如果 line_storage 是矩阵, 而实际线段的数目超过矩阵尺寸, 那么最大可能数目的线段被返回 ( 对于标准 hough 变换, 线段按照长度降序输出 ). Hough 变换变量, 是下面变量的其中之一 : CV_HOUGH_STANDARD - 传统或标准 Hough 变换. 每一个线段由两个浮点数 (ρ, θ) 表示, 其中 ρ 是直线与原点 (0,0) 之间的距离,θ 线段与 x- 轴之间的夹角 因此, 矩阵类型必须是 CV_32FC2 type. CV_HOUGH_PROBABILISTIC - 概率 Hough 变换 ( 如果图像包含一些长的线性分割, 则效率更高 ). 它返回线段分割而不是整个线段 每个分割用起点和终点来表示, 所以矩阵 ( 或创建的序列 ) 类型是 CV_32SC4. CV_HOUGH_MULTI_SCALE - 传统 Hough 变换的多尺度变种 线段的编码方式与 CV_HOUGH_STANDARD 的一致 rho 与象素相关单位的距离精度 theta 弧度测量的角度精度 threshold 阈值参数 如果相应的累计值大于 threshold, 则函数返回的这个线段. param1 第一个方法相关的参数 : 对传统 Hough 变换, 不使用 (0). 对概率 Hough 变换, 它是最小线段长度. 对多尺度 Hough 变换, 它是距离精度 rho 的分母 ( 大致的距离精度是 rho 而精确的应该是 rho / param1 ). param2 第二个方法相关参数 : 对传统 Hough 变换, 不使用 (0). 对概率 Hough 变换, 这个参数表示在同一条直线上进行碎线段连接的最大间隔值 (gap), 即当同一条直线上的两条碎线段之间的间隔小于 param2 时, 将其合二为一 对多尺度 Hough 变换, 它是角度精度 theta 的分母 ( 大致的角度精度是 theta 而精确的角度应该是 theta / param2). 函数 cvhoughlines2 实现了用于线段检测的不同 Hough 变换方法. Example. 用 Hough transform 检测线段 /* This is a standalone program. Pass an image name as a first parameter 353

354 of the program.switch between standard and probabilistic Hough transform by changing "#if 1" to "#if 0" and back */ #include <cv.h> #include <highgui.h> #include <math.h> int main(int argc, char** argv) IplImage* src; if( argc == 2 && (src=cvloadimage(argv[1], 0))!= 0) IplImage* dst = cvcreateimage( cvgetsize(src), 8, 1 ); IplImage* color_dst = cvcreateimage( cvgetsize(src), 8, 3 ); CvMemStorage* storage = cvcreatememstorage(0); CvSeq* lines = 0; int i; IplImage* src1=cvcreateimage(cvsize(src->width,src->height),ipl_depth_8u,1); cvcvtcolor(src, src1, CV_BGR2GRAY); cvcanny( src1, dst, 50, 200, 3 ); cvcvtcolor( dst, color_dst, CV_GRAY2BGR ); #if 1 lines = cvhoughlines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 150, 0, 0 ); for( i = 0; i < lines->total; i++ ) float* line = (float*)cvgetseqelem(lines,i); float rho = line[0]; float theta = line[1]; CvPoint pt1, pt2; double a = cos(theta), b = sin(theta); if( fabs(a) < ) pt1.x = pt2.x = cvround(rho); pt1.y = 0; pt2.y = color_dst->height; } else if( fabs(b) < ) 354

355 pt1.y = pt2.y = cvround(rho); pt1.x = 0; pt2.x = color_dst->width; } else pt1.x = 0; pt1.y = cvround(rho/b); pt2.x = cvround(rho/a); pt2.y = 0; } cvline( color_dst, pt1, pt2, CV_RGB(255,0,0), 3, 8 ); } #else lines = cvhoughlines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 80, 30, 10 ); for( i = 0; i < lines->total; i++ ) CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i); cvline( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, 8 ); } #endif cvnamedwindow( "Source", 1 ); cvshowimage( "Source", src ); cvnamedwindow( "Hough", 1 ); cvshowimage( "Hough", color_dst ); } } cvwaitkey(0); 这是函数所用的样本图像 : 355

356 下面是程序的输出, 采用概率 Hough transform ("#if 0" 的部分 ): HoughCircles 利用 Hough 变换在灰度图像中找圆 CvSeq* cvhoughcircles( CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1=100, double param2=100, int min_radius=0, int max_radius=0 ); image 输入 8- 比特 单通道灰度图像. circle_storage 检测到的圆存储仓. 可以是内存存储仓 ( 此种情况下, 一个线段序列在存储仓中被创建, 并且由函数返回 ) 或者是包含圆参数的特殊类型的具有单行 / 单列的 CV_32FC3 356

357 型矩阵 (CvMat*). 矩阵头为函数所修改, 使得它的 cols/rows 将包含一组检测到的圆 如果 circle_storage 是矩阵, 而实际圆的数目超过矩阵尺寸, 那么最大可能数目的圆被返回. 每个圆由三个浮点数表示 : 圆心坐标 (x,y) 和半径. method dp Hough 变换方式, 目前只支持 CV_HOUGH_GRADIENT, which is basically 21HT, described in [Yuen03]. 累加器图像的分辨率 这个参数允许创建一个比输入图像分辨率低的累加器 ( 这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴 ) 如果 dp 设置为 1, 则分辨率是相同的 ; 如果设置为更大的值 ( 比如 2), 累加器的分辨率受此影响会变小 ( 此情况下为一半 ) dp 的值不能比 1 小 Resolution of the accumulator used to detect centers of the circles. For example, if it is 1, the accumulator will have the same resolution as the input image, if it is 2 - accumulator will have twice smaller width and height, etc. min_dist 该参数是让算法能明显区分的两个不同圆之间的最小距离 Minimum distance between centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed. param1 用于 Canny 的边缘阀值上限, 下限被置为上限的一半 The first method-specific parameter. In case of CV_HOUGH_GRADIENT it is the higher threshold of the two passed to Canny edge detector (the lower one will be twice smaller). param2 累加器的阀值 The second method-specific parameter. In case of CV_HOUGH_GRADIENT it is accumulator threshold at the center detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first. min_radius 最小圆半径 Minimal radius of the circles to search for. max_radius 最大圆半径 Maximal radius of the circles to search for. By default the maximal radius is set to max(image_width, image_height). 357

358 The function cvhoughcircles finds circles in grayscale image using some modification of Hough transform. Example. Detecting circles with Hough transform. #include <cv.h> #include <highgui.h> #include <math.h> int main(int argc, char** argv) IplImage* img; if( argc == 2 && (img=cvloadimage(argv[1], 1))!= 0) IplImage* gray = cvcreateimage( cvgetsize(img), 8, 1 ); CvMemStorage* storage = cvcreatememstorage(0); cvcvtcolor( img, gray, CV_BGR2GRAY ); cvsmooth( gray, gray, CV_GAUSSIAN, 9, 9 ); // smooth it, otherwise a lot of false circles may be detected CvSeq* circles = cvhoughcircles( gray, storage, CV_HOUGH_GRADIENT, 2, gray->height/4, 200, 100 ); int i; for( i = 0; i < circles->total; i++ ) float* p = (float*)cvgetseqelem( circles, i ); cvcircle( img, cvpoint(cvround(p[0]),cvround(p[1])), 3, CV_RGB(0,255,0), -1, 8, 0 ); cvcircle( img, cvpoint(cvround(p[0]),cvround(p[1])), cvround(p[2]), CV_RGB(255,0,0), 3, 8, 0 ); } cvnamedwindow( "circles", 1 ); cvshowimage( "circles", img ); } return 0; } DistTransform 计算输入图像的所有非零元素对其最近零元素的距离 void cvdisttransform( const CvArr* src, CvArr* dst, int distance_type=cv_dist_l2, int mask_size=3, const float* mask=null ); src 358

359 输入 8- 比特 单通道 ( 二值 ) 图像. dst 含计算出的距离的输出图像 (32- 比特 浮点数 单通道 ). distance_type 距离类型 ; 可以是 CV_DIST_L1, CV_DIST_L2, CV_DIST_C 或 CV_DIST_USER. mask_size 距离变换掩模的大小, 可以是 3 或 5. 对 CV_DIST_L1 或 CV_DIST_C 的情况, 参数值被强制设定为 3, 因为 3 3 mask 给出 5 5 mask 一样的结果, 而且速度还更快 mask 用户自定义距离情况下的 mask 在 3 3 mask 下它由两个数 ( 水平 / 垂直位量, 对角线位移量 ) 组成, 5 5 mask 下由三个数组成 ( 水平 / 垂直位移量, 对角位移和国际象棋里的马步 ( 马走日 )) 函数 cvdisttransform 二值图像每一个象素点到它最邻近零象素点的距离 对零象素, 函数设置 0 距离, 对其它象素, 它寻找由基本位移 ( 水平 垂直 对角线或 knight's move, 最后一项对 5 5 mask 有用 ) 构成的最短路径 全部的距离被认为是基本距离的和 由于距离函数是对称的, 所有水平和垂直位移具有同样的代价 ( 表示为 a ), 所有的对角位移具有同样的代价 ( 表示为 b), 所有的 knight's 移动具有同样的代价 ( 表示为 c). 对类型 CV_DIST_C 和 CV_DIST_L1, 距离的计算是精确的, 而类型 CV_DIST_L2 ( 欧式距离 ) 距离的计算有某些相对误差 (5 5 mask 给出更精确的结果 ), OpenCV 使用 [Borgefors86] 推荐的值 : CV_DIST_C (3 3): a=1, b=1 CV_DIST_L1 (3 3): a=1, b=2 CV_DIST_L2 (3 3): a=0.955, b= CV_DIST_L2 (5 5): a=1, b=1.4, c= 下面用户自定义距离的的距离域示例 ( 黑点 (0) 在白色方块中间 ): 用户自定义 3 3 mask (a=1, b=1.5) 用户自定义 5 5 mask (a=1, b=1.5, c=2) 359

360 典型的使用快速粗略距离估计 CV_DIST_L2, 3 3 mask, 如果要更精确的距离估计, 使用 CV_DIST_L2, 5 5 mask When the output parameter labels is not NULL, for every non-zero pixel the function also finds the nearest connected component consisting of zero pixels. The connected components themselves are found as contours in the beginning of the function. In this mode the processing time is still O(N), where N is the number of pixels. Thus, the function provides a very fast way to compute approximate Voronoi diagram for the binary image. Inpaint 修复图像中选择区域 void cvinpaint( const CvArr* src, const CvArr* mask, CvArr* dst, int flags, double inpaintradius ); src 输入 8 比特单通道或者三通道图像 mask 修复图像的掩饰,8 比特单通道图像 非零像素表示该区域需要修复 dst 输出图像, 和输入图像相同格式相同大小 flags 修复方法, 以下之一 : CV_INPAINT_NS - 基于 Navier-Stokes 的方法 CV_INPAINT_TELEA - Alexandru Telea[Telea04] 的方法 inpaintradius 算法考虑的每个修复点的圆形领域的半径 函数 cvinpaint 从选择图像区域边界的像素重建该区域 函数可以用来去除扫描相片的灰尘或者刮伤, 或者从静态图像或者视频中去除不需要的物体 直方图 CvHistogram 360

361 多维直方图 typedef struct CvHistogram int type; CvArr* bins; float thresh[cv_max_dim][2]; /* for uniform histograms */ float** thresh2; /* for non-uniform histograms */ CvMatND mat; /* embedded matrix header for array histograms */ } CvHistogram; bins : 用于存放直方图每个灰度级数目的数组指针, 数组在 cvcreatehist 的时候创建, 其维数由 cvcreatehist 确定 ( 一般以一维比较常见 ) CreateHist 创建直方图 CvHistogram* cvcreatehist( int dims, int* sizes, int type, float** ranges=null, int uniform=1 ); dims 直方图维数的数目 sizes 直方图维数尺寸的数组 type 直方图的表示格式 : CV_HIST_ARRAY 意味着直方图数据表示为多维密集数组 CvMatND; CV_HIST_TREE 意味着直方图数据表示为多维稀疏数组 CvSparseMat. ranges 图中方块范围的数组. 它的内容取决于参数 uniform 的值 这个范围的用处是确定何时计算直方图或决定反向映射 (backprojected ), 每个方块对应于输入图像的哪个 / 哪组值 uniform 归一化标识 如果不为 0, 则 ranges[i](0<=i<cdims, 译者注 :cdims 为直方图的维数, 对于灰度图为 1, 彩色图为 3) 是包含两个元素的范围数组, 包括直方图第 i 维的上界和下界 在第 i 维上的整个区域 [lower,upper] 被分割成 dims[i] 个相等的块 ( 译者注 :dims[i] 表示直方图第 i 维的块数 ), 这些块用来确定输入象素的第 i 个值 ( 译者注 : 对于彩色图像,i 确定 R, G, 或者 B) 的对应的块 ; 如果为 0, 则 ranges[i] 是包含 dims[i]+1 个元素的范围数组, 包括 lower0, upper0, lower1, upper1 == lower2,..., upperdims[i]-1, 其中 lowerj 和 upperj 分别是直方图第 i 维上第 j 个方块的上下界 ( 针对输入象素的第 i 个值 ) 任何情况下, 输入值如果超出了一个直方块所指定的范围外, 都不会被 cvcalchist 计数, 而且会被函数 cvcalcbackproject 置零 361

362 函数 cvcreatehist 创建一个指定尺寸的直方图, 并且返回创建的直方图的指针 如果数组的 ranges 是 0, 则直方块的范围必须由函数 cvsethistbinranges 稍后指定 虽然 cvcalchist 和 cvcalcbackproject 可以处理 8- 比特图像而无需设置任何直方块的范围, 但它们都被假设等分 之间的空间 SetHistBinRanges 设置直方块的区间 void cvsethistbinranges( CvHistogram* hist, float** ranges, int uniform=1 ); hist 直方图. ranges 直方块范围数组的数组, 见 cvcreatehist. uniform 归一化标识, 见 cvcreatehist. 函数 cvsethistbinranges 是一个独立的函数, 完成直方块的区间设置 更多详细的关于参数 ranges 和 uniform 的描述, 请参考函数 cvcalchist, 该函数也可以初始化区间 直方块的区间的设置必须在计算直方图之前, 或在计算直方图的反射图之前 ReleaseHist 释放直方图结构 void cvreleasehist( CvHistogram** hist ); hist 被释放的直方图结构的双指针. 函数 cvreleasehist 释放直方图 ( 头和数据 ). 指向直方图的指针被函数所清空 如果 *hist 指针已经为 NULL, 则函数不做任何事情 ClearHist 清除直方图 void cvclearhist( CvHistogram* hist ); hist 直方图. 362

363 函数 cvclearhist 当直方图是稠密数组时将所有直方块设置为 0, 当直方图是稀疏数组时, 除去所有的直方块 MakeHistHeaderForArray 从数组中创建直方图 CvHistogram* cvmakehistheaderforarray( int dims, int* sizes, CvHistogram* hist, float* data, float** ranges=null, int uniform=1 ); dims 直方图维数. sizes 直方图维数尺寸的数组 hist 为函数所初始化的直方图头 data 用来存储直方块的数组 ranges 直方块范围, 见 cvcreatehist. uniform 归一化标识, 见 cvcreatehist. 函数 cvmakehistheaderforarray 初始化直方图, 其中头和直方块为用户所分配 以后不需要调用 cvreleasehist 只有稠密直方图可以采用这种方法, 函数返回 hist. QueryHistValue_1D 查询直方块的值 #define cvqueryhistvalue_1d( hist, idx0 ) \ cvgetreal1d( (hist)->bins, (idx0) ) #define cvqueryhistvalue_2d( hist, idx0, idx1 ) \ cvgetreal2d( (hist)->bins, (idx0), (idx1) ) #define cvqueryhistvalue_3d( hist, idx0, idx1, idx2 ) \ cvgetreal3d( (hist)->bins, (idx0), (idx1), (idx2) ) #define cvqueryhistvalue_nd( hist, idx ) \ cvgetrealnd( (hist)->bins, (idx) ) hist 直方图 idx0, idx1, idx2, idx3 直方块的下标索引 idx 363

364 下标数组宏 cvqueryhistvalue_*d 返回 1D, 2D, 3D 或 N-D 直方图的指定直方块的值 对稀疏直方图, 如果方块在直方图中不存在, 函数返回 0, 而且不创建新的直方块 GetHistValue_1D 返回直方块的指针 #define cvgethistvalue_1d( hist, idx0 ) \ ((float*)(cvptr1d( (hist)->bins, (idx0), 0 )) #define cvgethistvalue_2d( hist, idx0, idx1 ) \ ((float*)(cvptr2d( (hist)->bins, (idx0), (idx1), 0 )) #define cvgethistvalue_3d( hist, idx0, idx1, idx2 ) \ ((float*)(cvptr3d( (hist)->bins, (idx0), (idx1), (idx2), 0 )) #define cvgethistvalue_nd( hist, idx ) \ ((float*)(cvptrnd( (hist)->bins, (idx), 0 )) hist 直方图. idx0, idx1, idx2, idx3 直方块的下标索引. idx 下标数组宏 cvgethistvalue_*d 返回 1D, 2D, 3D 或 N-D 直方图的指定直方块的指针 对稀疏直方图, 函数创建一个新的直方块, 且设置其为 0, 除非它已经存在 GetMinMaxHistValue 发现最大和最小直方块 void cvgetminmaxhistvalue( const CvHistogram* hist, float* min_value, float* max_value, int* min_idx=null, int* max_idx=null ); hist 直方图 min_value 直方图最小值的指针 max_value 直方图最大值的指针 min_idx 数组中最小坐标的指针 max_idx 数组中最大坐标的指针 364

365 函数 cvgetminmaxhistvalue 发现最大和最小直方块以及它们的位置 任何输出变量都是可选的 在具有同样值几个极值中, 返回具有最小下标索引 ( 以字母排列顺序定 ) 的那一个 NormalizeHist 归一化直方图 void cvnormalizehist( CvHistogram* hist, double factor ); hist factor 直方图的指针. 归一化因子 函数 cvnormalizehist 通过缩放来归一化直方块, 使得所有块的和等于 factor. ThreshHist 对直方图取阈值 void cvthreshhist( CvHistogram* hist, double threshold ); hist 直方图的指针. threshold 阈值大小函数 cvthreshhist 清除那些小于指定阈值得直方块 CompareHist 比较两个稠密直方图 double cvcomparehist( const CvHistogram* hist1, const CvHistogram* hist2, int method ); hist1 hist2 method 第一个稠密直方图 第二个稠密直方图 比较方法, 采用其中之一 : CV_COMP_CORREL CV_COMP_CHISQR CV_COMP_INTERSECT CV_COMP_BHATTACHARYYA 365

366 函数 cvcomparehist 采用下面指定的方法比较两个稠密直方图 (H1 表示第一个,H2 表示第二个 ): Correlation (method=cv_comp_correl): 其中 number of histogram bins) (N 是 number of histogram bins) (N 是 Chi-square(method=CV_COMP_CHISQR): 交叉 (method=cv_comp_intersect): d(h1,h2) = min(h1(i),h2(i)) i Bhattacharyya 距离 (method=cv_comp_bhattacharyya): 函数返回 d(h1,h2) 的值 注意 :Bhattacharyya 距离只能应用到规一化后的直方图 为了比较稀疏直方图或更一般的加权稀疏点集 ( 译者注 : 直方图匹配是图像检索中的常用方法 ), 考虑使用函数 cvcalcemd CopyHist 拷贝直方图 void cvcopyhist( const CvHistogram* src, CvHistogram** dst ); src 输入的直方图 366

367 dst 输出的直方图指针 函数 cvcopyhist 对直方图作拷贝 如果第二个直方图指针 *dst 是 NULL, 则创建一个与 src 同样大小的直方图 否则, 两个直方图必须大小和类型一致 然后函数将输入的直方块的值复制到输出的直方图中, 并且设置取值范围与 src 的一致 CalcHist 计算图像 image(s) 的直方图 void cvcalchist( IplImage** image, CvHistogram* hist, int accumulate=0, const CvArr* mask=null ); image 输入图像 s ( 虽然也可以使用 CvMat** ). hist 直方图指针 accumulate 累计标识 如果设置, 则直方图在开始时不被清零 这个特征保证可以为多个图像计算一个单独的直方图, 或者在线更新直方图 mask 操作 mask, 确定输入图像的哪个象素被计数函数 cvcalchist 计算一张或多张单通道图像的直方图 ( 译者注 : 若要计算多通道, 可像以下例子那样用多个单通道图来表示 ) 用来增加直方块的数组元素可从相应输入图像的同样位置提取 Sample. 计算和显示彩色图像的 2D 色调 - 饱和度图像 #include <cv.h> #include <highgui.h> int main( int argc, char** argv ) IplImage* src; if( argc == 2 && (src=cvloadimage(argv[1], 1))!= 0) IplImage* h_plane = cvcreateimage( cvgetsize(src), 8, 1 ); IplImage* s_plane = cvcreateimage( cvgetsize(src), 8, 1 ); IplImage* v_plane = cvcreateimage( cvgetsize(src), 8, 1 ); IplImage* planes[] = h_plane, s_plane }; IplImage* hsv = cvcreateimage( cvgetsize(src), 8, 3 ); int h_bins = 30, s_bins = 32; int hist_size[] = h_bins, s_bins}; /* hue varies from 0 (~0 red) to 180 (~360 red again) */ float h_ranges[] = 0, 180 }; 367

368 /* saturation varies from 0 (black-gray-white) to 255 (pure spectrum color) */ float s_ranges[] = 0, 255 }; float* ranges[] = h_ranges, s_ranges }; int scale = 10; IplImage* hist_img = cvcreateimage( cvsize(h_bins*scale,s_bins*scale), 8, 3 ); CvHistogram* hist; float max_value = 0; int h, s; cvcvtcolor( src, hsv, CV_BGR2HSV ); cvcvtpixtoplane( hsv, h_plane, s_plane, v_plane, 0 ); hist = cvcreatehist( 2, hist_size, CV_HIST_ARRAY, ranges, 1 ); cvcalchist( planes, hist, 0, 0 ); cvgetminmaxhistvalue( hist, 0, &max_value, 0, 0 ); cvzero( hist_img ); for( h = 0; h < h_bins; h++ ) for( s = 0; s < s_bins; s++ ) float bin_val = cvqueryhistvalue_2d( hist, h, s ); int intensity = cvround(bin_val*255/max_value); cvrectangle( hist_img, cvpoint( h*scale, s*scale ), cvpoint( (h+1)*scale - 1, (s+1)*scale - 1), CV_RGB(intensity,intensity,intensity), /* graw a grayscale histogram. if you have idea how to do it nicer let us know */ } } CV_FILLED ); cvnamedwindow( "Source", 1 ); cvshowimage( "Source", src ); cvnamedwindow( "H-S Histogram", 1 ); cvshowimage( "H-S Histogram", hist_img ); cvwaitkey(0); 368

369 } } CalcBackProject 计算反向投影 void cvcalcbackproject( IplImage** image, CvArr* back_project, const CvHistogram* hist ); image 输入图像 ( 也可以传递 CvMat** ). back_project 反向投影图像, 与输入图像具有同样类型. hist 直方图函数 cvcalcbackproject 计算直方图的反向投影. 对于所有输入的单通道图像同一位置的象素数组, 该函数根据相应的象素数组 (RGB), 放置其对应的直方块的值到输出图像中 用统计学术语, 输出图像象素点的值是观测数组在某个分布 ( 直方图 ) 下的概率 例如, 为了发现图像中的红色目标, 可以这么做 : 1. 对红色物体计算色调直方图, 假设图像仅仅包含该物体 则直方图有可能有极值, 对应着红颜色 2. 对将要搜索目标的输入图像, 使用直方图计算其色调平面的反向投影, 然后对图像做阈值操作 3. 在产生的图像中发现连通部分, 然后使用某种附加准则选择正确的部分, 比如最大的连通部分 这是 Camshift 彩色目标跟踪器中的一个逼进算法, 除了第三步,CAMSHIFT 算法使用了上一次目标位置来定位反向投影中的目标 CalcBackProjectPatch 用直方图比较来定位图像中的模板 void cvcalcbackprojectpatch( IplImage** image, CvArr* dst, CvSize patch_size, CvHistogram* hist, int method, double factor ); image dst 输入图像 ( 可以传递 CvMat** ) 输出图像. 369

370 patch_size 扫描输入图像的补丁尺寸 hist 直方图 method 比较方法, 传递给 cvcomparehist ( 见该函数的描述 ). factor 直方图的归一化因子, 将影响输出图像的归一化缩放 如果为 1, 则不定 /* 归一化因子的类型实际上是 double, 而非 float*/ 函数 cvcalcbackprojectpatch 通过输入图像补丁的直方图和给定直方图的比较, 来计算反向投影 提取图像在 ROI 中每一个位置的某种测量结果产生了数组 image. 这些结果可以是色调, x 差分, y 差分, Laplacian 滤波器, 有方向 Gabor 滤波器等中的一个或多个 每种测量输出都被划归为它自己的单独图像 image 图像数组是这些测量图像的集合 一个多维直方图 hist 从这些图像数组中被采样创建 最后直方图被归一化 直方图 hist 的维数通常很大等于图像数组 image 的元素个数 在选择的 ROI 中, 每一个新的图像被测量并且转换为一个图像数组 在以锚点为 补丁 中心的图像 image 区域中计算直方图 ( 如下图所示 ) 用参数 norm_factor 来归一化直方图, 使得它可以与 hist 互相比较 计算出的直方图与直方图模型互相比较, (hist 使用函数 cvcomparehist, 比较方法是 method=method). 输出结果被放置到概率图像 dst 补丁锚点的对应位置上 这个过程随着补丁滑过整个 ROI 而重复进行 迭代直方图的更新可以通过在原直方图中减除 补丁 已复盖的尾象素点或者加上新复盖的象素点来实现, 这种更新方式可以节省大量的操作, 尽管目前在函数体中还没有实现 Back Project Calculation by Patches CalcProbDensity 两个直方图相除 370

371 void cvcalcprobdensity( const CvHistogram* hist1, const CvHistogram* hist2, CvHistogram* dst_hist, double scale=255 ); hist1 第一个直方图 ( 分母 ). hist2 第二个直方图 dst_hist 输出的直方图 scale 输出直方图的尺度因子 函数 cvcalcprobdensity 从两个直方图中计算目标概率密度 : dist_hist(i)=0 if hist1(i)==0 scale if hist1(i)!=0 && hist2(i)>hist1(i) hist2(i)*scale/hist1(i) if hist1(i)!=0 && hist2(i)<=hist1(i) 所以输出的直方块小于尺度因子 EqualizeHist 灰度图象直方图均衡化 void cvequalizehist( const CvArr* src, CvArr* dst ); src dst 输入的 8- 比特单信道图像 输出的图像与输入图像大小与数据类型相同 函数 cvequalizehist 采用如下法则对输入图像进行直方图均衡化 : 1. 计算输入图像的直方图 H 2. 直方图归一化, 因此直方块和为 计算直方图积分 : 4. 采用 H' 作为查询表 :dst(x,y)=h'(src(x,y)) 进行图像变换 该方法归一化图像亮度和增强对比度 例 : 彩色图像的直方图均衡化 371

372 int i; IplImage *pimagechannel[4] = 0, 0, 0, 0 }; psrcimage = cvloadimage( "test.jpg", 1 ) ; IplImage *pimage = cvcreateimage(cvgetsize(psrcimage), psrcimage->depth, psrcimage->nchannels); if( psrcimage ) for( i = 0; i < psrcimage->nchannels; i++ ) pimagechannel[i] = cvcreateimage( cvgetsize(psrcimage), psrcimage->depth, 1 ); } // 信道分离 cvsplit( psrcimage, pimagechannel[0], pimagechannel[1], pimagechannel[2], pimagechannel[3] ); for( i = 0; i < pimage->nchannels; i++ ) // 直方图均衡化 cvequalizehist( pimagechannel[i], pimagechannel[i] ); } // 信道组合 cvmerge( pimagechannel[0], pimagechannel[1], pimagechannel[2], pimagechannel[3], pimage ); // 图像显示代码 ( 略 ) // 释放资源 for( i = 0; i < psrcimage->nchannels; i++ ) if ( pimagechannel[i] ) cvreleaseimage( &pimagechannel[i] ); pimagechannel[i] = 0; } } cvreleaseimage( &pimage ); pimage = 0; } 匹配 MatchTemplate 比较模板和重叠的图像区域 372

373 void cvmatchtemplate( const CvArr* image, const CvArr* templ, CvArr* result, int method ); image templ result method 欲搜索的图像 它应该是单通道 8- 比特或 32- 比特浮点数图像 搜索模板, 不能大于输入图像, 且与输入图像具有一样的数据类型 比较结果的映射图像 单通道 32- 比特浮点数. 如果图像是 W H 而 templ 是 w h, 则 result 一定是 (W-w+1) (H-h+1). 指定匹配方法 : 函数 cvmatchtemplate 与函数 cvcalcbackprojectpatch 类似 它滑动过整个图像 image, 用指定方法比较 templ 与图像尺寸为 w h 的重叠区域, 并且将比较结果存到 result 中 下面是不同的比较方法, 可以使用其中的一种 (I 表示图像,T - 模板, R - 结果. 模板与图像重叠区域 x'=0..w-1, y'=0..h-1 之间求和 ): method=cv_tm_sqdiff: R(x,y) = [T(x',y') I(x + x',y + y')] 2 x',y' method=cv_tm_sqdiff_normed: method=cv_tm_ccorr: method=cv_tm_ccorr_normed: method=cv_tm_ccoeff: 其中 brightness=>0) (mean template brightness=>0) (mean template 373

374 (mean patch brightness=>0) (mean patch brightness=>0) method=cv_tm_ccoeff_normed: 函数完成比较后, 通过使用 cvminmaxloc 找全局最小值 CV_TM_SQDIFF*) 或者最大值 (CV_TM_CCORR* and CV_TM_CCOEFF*) MatchShapes 比较两个形状 double cvmatchshapes( const void* object1, const void* object2, int method, double parameter=0 ); object1 第一个轮廓或灰度图像 object2 第二个轮廓或灰度图像 method 比较方法, 其中之一 CV_CONTOUR_MATCH_I1, CV_CONTOURS_MATCH_I2 or CV_CONTOURS_MATCH_I3. parameter 比较方法的参数 ( 目前不用 ). 函数 cvmatchshapes 比较两个形状 三个实现方法全部使用 Hu 矩 ( 见 cvgethumoments) (A ~ object1, B - object2): method=cv_contour_match_i1: method=cv_contour_match_i2: method=cv_contour_match_i3: 其中 374

375 ,,,, 是 A 和 B 的 Hu 矩. CalcEMD2 两个加权点集之间计算最小工作距离 float cvcalcemd2( const CvArr* signature1, const CvArr* signature2, int distance_type, CvDistanceFunction distance_func=null, const CvArr* cost_matrix=null, CvArr* flow=null, float* lower_bound=null, void* userdata=null ); typedef float (*CvDistanceFunction)(const float* f1, const float* f2, void* userdata); signature1 第一个签名, 大小为 size1 (dims+1) 的浮点数矩阵, 每一行依次存储点的权重和点的坐标 矩阵允许只有一列 ( 即仅有权重 ), 如果使用用户自定义的代价矩阵 signature2 第二个签名, 与 signature1 的格式一样 size2 (dims+1), 尽管行数可以不同 ( 列数要相同 ) 当一个额外的虚拟点加入 signature1 或 signature2 中的时候, 权重也可不同 distance_type 使用的准则, CV_DIST_L1, CV_DIST_L2, 和 CV_DIST_C 分别为标准的准则 CV_DIST_USER 意味着使用用户自定义函数 distance_func 或预先计算好的代价矩阵 cost_matrix distance_func 用户自定义的距离函数 用两个点的坐标计算两点之间的距离 cost_matrix 自定义大小为 size1 size2 的代价矩阵 cost_matrix 和 distance_func 两者至少有一个必须为 NULL. 而且, 如果使用代价函数, 下边界无法计算, 因为它需要准则函数 flow 产生的大小为 size1 size2 流矩阵 (flow matrix): flowij 是从 signature1 的第 i 个点到 signature2 的第 j 个点的流 (flow) lower_bound 可选的输入 / 输出参数 : 两个签名之间的距离下边界, 是两个质心之间的距离 如果使用自定义代价矩阵, 点集的所有权重不等, 或者有签名只包含权重 ( 即该签名矩阵只有单独一列 ), 则下边界也许不会计算 用户必须初始化 *lower_bound. 如果质心之间的距离大于获等于 *lower_bound ( 这意味着签名之间足够远 ), 函数则 375

376 不计算 EMD. 任何情况下, 函数返回时 *lower_bound 都被设置为计算出来的质心距离 因此如果用户想同时计算质心距离和 T EMD, *lower_bound 应该被设置为 0. userdata 传输到自定义距离函数的可选数据指针函数 cvcalcemd2 计算两个加权点集之间的移动距离或距离下界 在 [RubnerSept98] 中所描述的其中一个应用就是图像提取得多维直方图比较 EMD 是一个使用某种单纯形算法 (simplex algorithm) 来解决的交通问题 其计算复杂度在最坏情况下是指数形式的, 但是平均而言它的速度相当快 对实的准则, 下边界的计算可以更快 ( 使用线性时间算法 ), 且它可用来粗略确定两个点集是否足够远以至无法联系到同一个目标上 取自 " 原文网址 : Cv 结构分析 轮廓处理函数 ApproxChains 用多边形曲线逼近 Freeman 链 CvSeq* cvapproxchains( CvSeq* src_seq, CvMemStorage* storage, int method=cv_chain_approx_simple, double parameter=0, int minimal_perimeter=0, int recursive=0 ); src_seq 涉及其它链的链指针 storage 存储多边形线段位置的缓存 method 逼近方法 ( 见函数 cvfindcontours 的描述 ). parameter 方法参数 ( 现在不用 ). minimal_perimeter 仅逼近周长大于 minimal_perimeter 轮廓 其它的链从结果中除去 recursive 如果非 0, 函数从 src_seq 中利用 h_next 和 v_next links 连接逼近所有可访问的链 如果为 0, 则仅逼近单链 376

377 这是一个单独的逼近程序 对同样的逼近标识, 函数 cvapproxchains 与 cvfindcontours 的工作方式一模一样 它返回发现的第一个轮廓的指针 其它的逼近模块, 可以用返回结构中的 v_next 和 v_next 域来访问 StartReadChainPoints 初始化链读取 void cvstartreadchainpoints( CvChain* chain, CvChainPtReader* reader ); chain reader 链的指针 链的读取状态 函数 cvstartreadchainpoints 初始化一个特殊的读取器 ( 参考 Dynamic Data Structures 以获得关于集合与序列的更多内容 ). ReadChainPoint 得到下一个链的点 CvPoint cvreadchainpoint( CvChainPtReader* reader ); reader 链的读取状态 函数 cvreadchainpoint 返回当前链的点, 并且更新读取位置 ApproxPoly 用指定精度逼近多边形曲线 CvSeq* cvapproxpoly( const void* src_seq, int header_size, CvMemStorage* storage, int method, double parameter, int parameter2=0 ); src_seq 点集数组序列 header_size 逼近曲线的头尺寸 storage 逼近轮廓的容器 如果为 NULL, 则使用输入的序列 method 逼近方法 目前仅支持 CV_POLY_APPROX_DP, 对应 Douglas-Peucker 算法. parameter 377

378 方法相关参数 对 CV_POLY_APPROX_DP 它是指定的逼近精度 parameter2 如果 src_seq 是序列, 它表示要么逼近单个序列, 要么在 src_seq 的同一个或低级层次上逼近所有序列 ( 参考 cvfindcontours 中对轮廓继承结构的描述 ). 如果 src_seq 是点集的数组 (CvMat*), 参数指定曲线是闭合 (parameter2!=0) 还是非闭合 (parameter2=0). 函数 cvapproxpoly 逼近一个或多个曲线, 并返回逼近结果 对多个曲线的逼近, 生成的树将与输入的具有同样的结构 (1:1 的对应关系 ). BoundingRect 计算点集的最外面 (up-right) 矩形边界 CvRect cvboundingrect( CvArr* points, int update=0 ); points update 二维点集, 点的序列或向量 (CvMat) 更新标识 下面是轮廓类型和标识的一些可能组合 : update=0, contour ~ CvContour*: 不计算矩形边界, 但直接由轮廓头的 rect 域得到 update=1, contour ~ CvContour*: 计算矩形边界, 而且将结果写入到轮廓头的 rect 域中 header. update=0, contour ~ CvSeq* or CvMat*: 计算并返回边界矩形 update=1, contour ~ CvSeq* or CvMat*: 产生运行错误 (runtime error is raised) 函数 cvboundingrect 返回二维点集的最外面 (up-right) 矩形边界 ContourArea 计算整个轮廓或部分轮廓的面积 double cvcontourarea( const CvArr* contour, CvSlice slice=cv_whole_seq ); contour 轮廓 ( 边界点的序列或数组 ). slice 感兴趣轮廓部分的起始点, 缺省是计算整个轮廓的面积 函数 cvcontourarea 计算整个轮廓或部分轮廓的面积 对后面的情况, 面积表示轮廓部分和起始点连线构成的封闭部分的面积 如下图所示 : 378

379 备注 : 轮廓的方向影响面积的符号 因此函数也许会返回负的结果 应用函数 fabs() 得到面积的绝对值 ArcLength 计算轮廓周长或曲线长度 double cvarclength( const void* curve, CvSlice slice=cv_whole_seq, int is_closed=-1 ); curve 曲线点集序列或数组 slice 曲线的起始点, 缺省是计算整个曲线的长度 is_closed 表示曲线是否闭合, 有三种情况 : is_closed=0 - 假设曲线不闭合 is_closed>0 - 假设曲线闭合 is_closed<0 - 若曲线是序列, 检查 ((CvSeq*)curve)->flags 中的标识 CV_SEQ_FLAG_CLOSED 来确定曲线是否闭合 否则 ( 曲线由点集的数组 (CvMat*) 表示 ) 假设曲线不闭合 函数 cvarclength 通过依次计算序列点之间的线段长度, 并求和来得到曲线的长度 CreateContourTree 创建轮廓的继承表示形式 CvContourTree* cvcreatecontourtree( const CvSeq* contour, CvMemStorage* storage, double threshold ); contour 输入的轮廓 storage 379

380 输出树的容器 threshold 逼近精度函数 cvcreatecontourtree 为输入轮廓 contour 创建一个二叉树, 并返回树根的指针 如果参数 threshold 小于或等于 0, 则函数创建一个完整的二叉树 如果 threshold 大于 0, 函数用 threshold 指定的精度创建二叉树 : 如果基线的截断区域顶点小于 threshold, 该数就停止生长并作为函数的最终结果返回 ContourFromContourTree 由树恢复轮廓 CvSeq* cvcontourfromcontourtree( const CvContourTree* tree, CvMemStorage* storage, CvTermCriteria criteria ); tree 轮廓树 storage 重构的轮廓容器 criteria 停止重构的准则函数 cvcontourfromcontourtree 从二叉树恢复轮廓 参数 criteria 决定了重构的精度和使用树的数目及层次 所以它可建立逼近的轮廓 函数返回重构的轮廓 MatchContourTrees 用树的形式比较两个轮廓 double cvmatchcontourtrees( const CvContourTree* tree1, const CvContourTree* tree2, int method, double threshold ); tree1 第一个轮廓树 tree2 第二个轮廓树 method 相似度 仅支持 CV_CONTOUR_TREES_MATCH_I1 threshold 相似度阈值函数 cvmatchcontourtrees 计算两个轮廓树的匹配值 从树根开始通过逐层比较来计算相似度 如果某层的相似度小于 threshold, 则中断比较过程, 且返回当前的差值 380

381 计算几何 MaxRect 对两个给定矩形, 寻找矩形边界 CvRect cvmaxrect( const CvRect* rect1, const CvRect* rect2 ); rect1 rect2 第一个矩形 第二个矩形 函数 cvmaxrect 寻找包含两个输入矩形的具有最小面积的矩形边界 CvBox2D 旋转的二维盒子 typedef struct CvBox2D CvPoint2D32f center; /* 盒子的中心 */ CvSize2D32f size; /* 盒子的长和宽 */ float angle; /* 水平轴与第一个边的夹角, 用角度度表示 */ } CvBox2D; PointSeqFromMat 从点向量中初始化点序列头部 CvSeq* cvpointseqfrommat( int seq_kind, const CvArr* mat, CvContour* contour_header, CvSeqBlock* block ); seq_kind 381

382 点序列的类型 : 一系列点 (0), 曲线 (CV_SEQ_KIND_CURVE), 封闭曲线 (CV_SEQ_KIND_CURVE+CV_SEQ_FLAG_CLOSED) 等等 mat 输入矩阵 输入应该是连续的一维点向量, 类型也应该是 CV_32SC2 或者 CV_32FC2. contour_header 轮廓头部, 被函数初始化 block 序列块头部, 被函数初始化 函数 cvpointseqfrommat 初始化序列头部, 用来创建一个将给定矩阵中的元素形成的 " 虚拟 " 序列 没有数据被拷贝 被初始化的头部可以传递给其他任何包含输入点序列的函数 没有额外的元素加入序列, 但是一些可能被移除 函数是 cvmakeseqheaderforarray 的一个特别的变量, 然后在内部使用 它返回初始化头部的指针 需要注意的是, 包含的边界矩形 (CvContour 的 rect 字段 ) 没有被初始化, 如果你需要使用, 需要自己调用 cvboundingrect 以下是使用例子 CvContour header; CvSeqBlock block; CvMat* vector = cvcreatemat( 1, 3, CV_32SC2 ); CV_MAT_ELEM( *vector, CvPoint, 0, 0 ) = cvpoint(100,100); CV_MAT_ELEM( *vector, CvPoint, 0, 1 ) = cvpoint(100,200); CV_MAT_ELEM( *vector, CvPoint, 0, 2 ) = cvpoint(200,100); IplImage* img = cvcreateimage( cvsize(300,300), 8, 3 ); cvzero(img); cvdrawcontours( img, cvpointseqfrommat(cv_seq_kind_curve+cv_seq_flag_closed, vector, &header, &block), CV_RGB(255,0,0), CV_RGB(255,0,0), 0, 3, 8, cvpoint(0,0)); BoxPoints 寻找盒子的顶点 void cvboxpoints( CvBox2D box, CvPoint2D32f pt[4] ); box pt 盒子 顶点数组 函数 cvboxpoints 计算输入的二维盒子的顶点 下面是函数代码 : 382

383 void cvboxpoints( CvBox2D box, CvPoint2D32f pt[4] ) double angle = box.angle*cv_pi/180. float a = (float)cos(angle)*0.5f; float b = (float)sin(angle)*0.5f; } pt[0].x = box.center.x - a*box.size.height - b*box.size.width; pt[0].y = box.center.y + b*box.size.height - a*box.size.width; pt[1].x = box.center.x + a*box.size.height - b*box.size.width; pt[1].y = box.center.y - b*box.size.height - a*box.size.width; pt[2].x = 2*box.center.x - pt[0].x; pt[2].y = 2*box.center.y - pt[0].y; pt[3].x = 2*box.center.x - pt[1].x; pt[3].y = 2*box.center.y - pt[1].y; FitEllipse 二维点集的椭圆拟合 CvBox2D cvfitellipse2( const CvArr* points ); points 点集的序列或数组 函数 cvfitellipse 对给定的一组二维点集作椭圆的最佳拟合 ( 最小二乘意义上的 ) 返回的结构与 cvellipse 中的意义类似, 除了 size 表示椭圆轴的整个长度, 而不是一半长度 FitLine 2D 或 3D 点集的直线拟合 void cvfitline( const CvArr* points, int dist_type, double param, double reps, double aeps, float* line ); points 2D 或 3D 点集,32- 比特整数或浮点数坐标 dist_type 拟合的距离类型 ( 见讨论 ). param 对某些距离的数字参数, 如果是 0, 则选择某些最优值 reps, aeps 半径 ( 坐标原点到直线的距离 ) 和角度的精度, 一般设为

384 line 输出的直线参数 2D 拟合情况下, 它是包含 4 个浮点数的数组 (vx, vy, x0, y0), 其中 (vx, vy) 是线的单位向量而 (x0, y0) 是线上的某个点. 对 3D 拟合, 它是包含 6 个浮点数的数组 (vx, vy, vz, x0, y0, z0), 其中 (vx, vy, vz) 是线的单位向量, 而 (x0, y0, z0) 是线上某点 函数 cvfitline 通过求 sumi:ρ(ri) 的最小值方法, 用 2D 或 3D 点集拟合直线, 其中 ri 是第 i 个点到直线的距离, ρ(r) 是下面的距离函数之一 : dist_type=cv_dist_l2 (L2): ρ(r)=r 2 /2 ( 最简单和最快的最小二乘法 ) dist_type=cv_dist_l1 (L1): ρ(r)=r dist_type=cv_dist_l12 (L1-L2): ρ(r)=2 [sqrt(1+r 2 /2) - 1] dist_type=cv_dist_fair (Fair): ρ(r)=c 2 [r/c - log(1 + r/c)], C= dist_type=cv_dist_welsch (Welsch): ρ(r)=c 2 /2 [1 - exp(-(r/c) 2 )], C= dist_type=cv_dist_huber (Huber): ρ(r)= r 2 /2, if r < C; C (r-c/2), otherwise; C=1.345 ConvexHull2 发现点集的凸外形 CvSeq* cvconvexhull2( const CvArr* input, void* hull_storage=null, int orientation=cv_clockwise, int return_points=0 ); points 2D 点集的序列或数组,32- 比特整数或浮点数坐标 hull_storage 输出的数组 (CvMat*) 或内存缓存 (CvMemStorage*), 用以存储凸外形 如果是数组, 则它应该是一维的, 而且与输入的数组 / 序列具有同样数目的元素 输出时, 通过修改头结构将数组裁减到凸外形的尺寸 orientation 凸外形的旋转方向 : 逆时针或顺时针 (CV_CLOCKWISE or CV_COUNTER_CLOCKWISE) return_points 如果非零,hull_storage 为数组情况下, 点集将以外形 (hull) 存储, 而不是顶点形式 (indices) 如果 hull_storag 为内存存储模式下则存储为点集形式 (points) 函数 cvconvexhull2 使用 Sklansky 算法计算 2D 点集的凸外形 如果 hull_storage 是内存存储仓, 函数根据 return_points 的值, 创建一个包含外形的点集或指向这些点的指针的序列 例子. 由点集序列或数组创建凸外形 #include "cv.h" 384

385 #include "highgui.h" #include <stdlib.h> #define ARRAY 0 /* switch between array/sequence method by replacing 0<=>1 */ void main( int argc, char** argv ) IplImage* img = cvcreateimage( cvsize( 500, 500 ), 8, 3 ); cvnamedwindow( "hull", 1 ); #if!array CvMemStorage* storage = cvcreatememstorage(); #endif for(;;) int i, count = rand()% , hullcount; CvPoint pt0; #if!array CvSeq* ptseq = cvcreateseq( CV_SEQ_KIND_GENERIC CV_32SC2, sizeof(cvcontour), sizeof(cvpoint), storage ); CvSeq* hull; #else for( i = 0; i < count; i++ ) pt0.x = rand() % (img->width/2) + img->width/4; pt0.y = rand() % (img->height/2) + img->height/4; cvseqpush( ptseq, &pt0 ); } hull = cvconvexhull2( ptseq, 0, CV_CLOCKWISE, 0 ); hullcount = hull->total; CvPoint* points = (CvPoint*)malloc( count * sizeof(points[0])); int* hull = (int*)malloc( count * sizeof(hull[0])); CvMat point_mat = cvmat( 1, count, CV_32SC2, points ); CvMat hull_mat = cvmat( 1, count, CV_32SC1, hull ); for( i = 0; i < count; i++ ) pt0.x = rand() % (img->width/2) + img->width/4; pt0.y = rand() % (img->height/2) + img->height/4; 385

386 points[i] = pt0; } cvconvexhull2( &point_mat, &hull_mat, CV_CLOCKWISE, 0 ); hullcount = hull_mat.cols; #endif cvzero( img ); for( i = 0; i < count; i++ ) #if!array pt0 = *CV_GET_SEQ_ELEM( CvPoint, ptseq, i ); #else pt0 = points[i]; #endif cvcircle( img, pt0, 2, CV_RGB( 255, 0, 0 ), CV_FILLED ); } #if!array pt0 = **CV_GET_SEQ_ELEM( CvPoint*, hull, hullcount - 1 ); #else pt0 = points[hull[hullcount-1]]; #endif for( i = 0; i < hullcount; i++ ) #if!array CvPoint pt = **CV_GET_SEQ_ELEM( CvPoint*, hull, i ); #else CvPoint pt = points[hull[i]]; #endif cvline( img, pt0, pt, CV_RGB( 0, 255, 0 )); pt0 = pt; } cvshowimage( "hull", img ); int key = cvwaitkey(0); if( key == 27 ) // 'ESC' break; #if!array cvclearmemstorage( storage ); #else free( points ); 386

387 #endif } } free( hull ); CheckContourConvexity 测试轮廓的凸性 int cvcheckcontourconvexity( const CvArr* contour ); contour 被测试轮廓 ( 点序列或数组 ). 函数 cvcheckcontourconvexity 输入的轮廓是否为凸的 必须是简单轮廓, 比如没有自交叉 CvConvexityDefect 用来描述一个简单轮廓凸性缺陷的结构体 typedef struct CvConvexityDefect CvPoint* start; /* 缺陷开始的轮廓点 */ CvPoint* end; /* 缺陷结束的轮廓点 */ CvPoint* depth_point; /* 缺陷中距离凸形最远的轮廓点 ( 谷底 ) */ float depth; /* 谷底距离凸形的深度 */ } CvConvexityDefect; Picture. 手部轮廓的凸形缺陷. ConvexityDefects 387

388 发现轮廓凸形缺陷 CvSeq* cvconvexitydefects( const CvArr* contour, const CvArr* convexhull, CvMemStorage* storage=null ); contour 输入轮廓 convexhull 用 cvconvexhull2 得到的凸外形, 它应该包含轮廓的定点的指针或下标, 而不是外形点的本身, 即 cvconvexhull2 中的参数 return_points 应该设置为 0. storage 凸性缺陷的输出序列容器 如果为 NULL, 使用轮廓或外形的存储仓 函数 cvconvexitydefects 发现输入轮廓的所有凸性缺陷, 并且返回 CvConvexityDefect 结构序列 PointPolygonTest 测试点是否在多边形中 double cvpointpolygontest( const CvArr* contour, CvPoint2D32f pt, int measure_dist ); contour 输入轮廓. pt 针对轮廓需要测试的点 measure_dist 如果非 0, 函数将估算点到轮廓最近边的距离 函数 cvpointpolygontest 决定测试点是否在轮廓内, 轮廓外, 还是轮廓的边上 ( 或者共边的交点上 ), 它的返回值是正负零, 相对应的, 当 measure_dist=0 时, 返回值是 1, -1,0, 同样当 measure_dist 0, 它是返回一个从点到最近的边的带符号距离 388

389 下面是函数输出的结果, 用图片的每一个象素去测试轮廓的结果 MinAreaRect2 对给定的 2D 点集, 寻找最小面积的包围矩形 CvBox2D cvminarearect2( const CvArr* points, CvMemStorage* storage=null ); points 点序列或点集数组 storage 可选的临时存储仓函数 cvminarearect2 通过建立凸外形并且旋转外形以寻找给定 2D 点集的最小面积的包围矩形. Picture. Minimal-area bounding rectangle for contour MinEnclosingCircle 对给定的 2D 点集, 寻找最小面积的包围圆形 int cvminenclosingcircle( const CvArr* points, CvPoint2D32f* center, float* radius ); points 点序列或点集数组 389

OpenCV Tutorial

OpenCV Tutorial OpenCV Tutorial 潘纲 gpan@zju.edu.cn OpenCV 起源 Intel Performance Lib IPL,MKL,RPL,SPL IPP: Integrated Performance Primitives Video/Audio, Speech, Cryptography, IP, CV, Open Source Computer Vision Library

More information

fvalue = (pdata[y][i] + pdata[y][i + 1]) / 2; pdata[y][nhalfw + i] -= fvalue; fvalue = (pdata[y][nhalfw - 1] + pdata[y][nhalfw - 2]) / 2; pdata[y][nwi

fvalue = (pdata[y][i] + pdata[y][i + 1]) / 2; pdata[y][nhalfw + i] -= fvalue; fvalue = (pdata[y][nhalfw - 1] + pdata[y][nhalfw - 2]) / 2; pdata[y][nwi #include #include #include // 二维离散小波变换 ( 单通道浮点图像 ) void DWT(IplImage *pimage, int nlayer) // 执行条件 if (pimage) if (pimage->nchannels == 1 && pimage->depth == IPL_DEPTH_32F

More information

C/C++ - 文件IO

C/C++ - 文件IO C/C++ IO Table of contents 1. 2. 3. 4. 1 C ASCII ASCII ASCII 2 10000 00100111 00010000 31H, 30H, 30H, 30H, 30H 1, 0, 0, 0, 0 ASCII 3 4 5 UNIX ANSI C 5 FILE FILE 6 stdio.h typedef struct { int level ;

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: feis.tw@gmail.com 49 [P.51] C/C++ [P.52] [P.53] [P.55] (int) [P.57] (float/double) [P.58] printf scanf [P.59] [P.61] ( / ) [P.62] (char) [P.65] : +-*/% [P.67] : = [P.68] : ,

More information

****************************************************** Fundamentals of TV Tracking ****************************************************** ( ),,, :,,,,,, 1998 9 ( ISBN 7-118-01911-9),,, 1999 5 20 Email:

More information

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++;

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++; Memory & Pointer trio@seu.edu.cn 2.1 2.1.1 1 int *p int a 0x00C7 0x00C7 0x00C7 2.1.2 2 int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++; 2.1.3 1. 2. 3. 3 int A,

More information

C C C The Most Beautiful Language and Most Dangerous Language in the Programming World! C 2 C C C 4 C 40 30 10 Project 30 C Project 3 60 Project 40

C C C The Most Beautiful Language and Most Dangerous Language in the Programming World! C 2 C C C 4 C 40 30 10 Project 30 C Project 3 60 Project 40 C C trio@seu.edu.cn C C C C The Most Beautiful Language and Most Dangerous Language in the Programming World! C 2 C C C 4 C 40 30 10 Project 30 C Project 3 60 Project 40 Week3 C Week5 Week5 Memory & Pointer

More information

epub83-1

epub83-1 C++Builder 1 C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r 1.1 1.1.1 1-1 1. 1-1 1 2. 1-1 2 A c c e s s P a r a d o x Visual FoxPro 3. / C / S 2 C + + B u i l d e r / C

More information

Microsoft PowerPoint - Intel OPENCV

Microsoft PowerPoint - Intel OPENCV OPENCV 小引 洪晓鹏 2006-06-02 OPENCV 简介 OPENCV 的安装, 编译及使用 OPENCV 的基本数据结构和算法 1 OPENCV 简介 OPENCV 是什么 OPENCV 的地位 OPENCV 的优缺点 OPENCV OPENCV= Intel (c) Open source computer vision library OpenCV 是英特尔 开源计算机视觉库, 是

More information

四川省普通高等学校

四川省普通高等学校 四 川 省 普 通 高 等 学 校 计 算 机 应 用 知 识 和 能 力 等 级 考 试 考 试 大 纲 (2013 年 试 行 版 ) 四 川 省 教 育 厅 计 算 机 等 级 考 试 中 心 2013 年 1 月 目 录 一 级 考 试 大 纲 1 二 级 考 试 大 纲 6 程 序 设 计 公 共 基 础 知 识 6 BASIC 语 言 程 序 设 计 (Visual Basic) 9

More information

Microsoft Word - 3D手册2.doc

Microsoft Word - 3D手册2.doc 第 一 章 BLOCK 前 处 理 本 章 纲 要 : 1. BLOCK 前 处 理 1.1. 创 建 新 作 业 1.2. 设 定 模 拟 控 制 参 数 1.3. 输 入 对 象 数 据 1.4. 视 图 操 作 1.5. 选 择 点 1.6. 其 他 显 示 窗 口 图 标 钮 1.7. 保 存 作 业 1.8. 退 出 DEFORMTM3D 1 1. BLOCK 前 处 理 1.1. 创 建

More information

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO Car DVD New GUI IR Flow User Manual V0.1 Jan 25, 2008 19, Innovation First Road Science Park Hsin-Chu Taiwan 300 R.O.C. Tel: 886-3-578-6005 Fax: 886-3-578-4418 Web: www.sunplus.com Important Notice SUNPLUS

More information

coverage2.ppt

coverage2.ppt Satellite Tool Kit STK/Coverage STK 82 0715 010-68745117 1 Coverage Definition Figure of Merit 2 STK Basic Grid Assets Interval Description 3 Grid Global Latitude Bounds Longitude Lines Custom Regions

More information

FY.DOC

FY.DOC 高 职 高 专 21 世 纪 规 划 教 材 C++ 程 序 设 计 邓 振 杰 主 编 贾 振 华 孟 庆 敏 副 主 编 人 民 邮 电 出 版 社 内 容 提 要 本 书 系 统 地 介 绍 C++ 语 言 的 基 本 概 念 基 本 语 法 和 编 程 方 法, 深 入 浅 出 地 讲 述 C++ 语 言 面 向 对 象 的 重 要 特 征 : 类 和 对 象 抽 象 封 装 继 承 等 主

More information

mvc

mvc Build an application Tutor : Michael Pan Application Source codes - - Frameworks Xib files - - Resources - ( ) info.plist - UIKit Framework UIApplication Event status bar, icon... delegation [UIApplication

More information

WinMDI 28

WinMDI 28 WinMDI WinMDI 2 Region Gate Marker Quadrant Excel FACScan IBM-PC MO WinMDI WinMDI IBM-PC Dr. Joseph Trotter the Scripps Research Institute WinMDI HP PC WinMDI WinMDI PC MS WORD, PowerPoint, Excel, LOTUS

More information

C++ 程式設計

C++ 程式設計 C C 料, 數, - 列 串 理 列 main 數串列 什 pointer) 數, 數, 數 數 省 不 不, 數 (1) 數, 不 數 * 料 * 數 int *int_ptr; char *ch_ptr; float *float_ptr; double *double_ptr; 數 (2) int i=3; int *ptr; ptr=&i; 1000 1012 ptr 數, 數 1004

More information

ebook50-11

ebook50-11 11 Wi n d o w s C A D 53 M F C 54 55 56 57 58 M F C 11.1 53 11-1 11-1 MFC M F C C D C Wi n d o w s Wi n d o w s 4 11 199 1. 1) W M _ PA I N T p W n d C W n d C D C * p D C = p W n d GetDC( ); 2) p W n

More information

C/C++程序设计 - 字符串与格式化输入/输出

C/C++程序设计 - 字符串与格式化输入/输出 C/C++ / Table of contents 1. 2. 3. 4. 1 i # include # include // density of human body : 1. 04 e3 kg / m ^3 # define DENSITY 1. 04 e3 int main ( void ) { float weight, volume ; int

More information

ebook111-4

ebook111-4 Flash 4 Flash 4 F l a s h 5 Flash 4 Flash Flash 4 Flash 4 Flash 4 4.1 Flash 4 Flash 4 Flash 4 Flash Flash 4 Flash 4 4.2 Flash 4 Flash 4 A Flash 4 S h i f t F i l e P r e f e r e n c e s > > Flash 4 Flash

More information

2/80 2

2/80 2 2/80 2 3/80 3 DSP2400 is a high performance Digital Signal Processor (DSP) designed and developed by author s laboratory. It is designed for multimedia and wireless application. To develop application

More information

Move Component Object selection Component selection UV Maya Hotkeys editor Maya USING MAYA POLYGONAL MODELING 55

Move Component Object selection Component selection UV Maya Hotkeys editor Maya USING MAYA POLYGONAL MODELING 55 3 55 62 63 Move Component 63 70 72 73 73 Object selection Component selection UV Maya Hotkeys editor Maya 55 USING MAYA POLYGONAL MODELING Maya: Essentials Maya Essentials F8 Ctrl F9 Vertex/Face F9 F10

More information

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File 51 C 51 51 C C C C C C * 2003-3-30 pnzwzw@163.com C C C C KEIL uvision2 MCS51 PLM C VC++ 51 KEIL51 KEIL51 KEIL51 KEIL 2K DEMO C KEIL KEIL51 P 1 1 1 1-1 - 1 Project New Project 1 2 Windows 1 3 N C test

More information

Microsoft Word - KSAE06-S0262.doc

Microsoft Word - KSAE06-S0262.doc Stereo Vision based Forward Collision Warning and Avoidance System Yunhee LeeByungjoo KimHogi JungPaljoo Yoon Central R&D Center, MANDO Corporation, 413-5, Gomae-Ri, Gibeung-Eub, Youngin-Si, Kyonggi-Do,

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: feis.tw@gmail.com 9 [P.11] : Dev C++ [P.12] : http://c.feis.tw [P.13] [P.14] [P.15] [P.17] [P.23] Dev C++ [P.24] [P.27] [P.34] C / C++ [P.35] 10 C / C++ C C++ C C++ C++ C ( ) C++

More information

Bus Hound 5

Bus Hound 5 Bus Hound 5.0 ( 1.0) 21IC 2007 7 BusHound perisoft PC hound Bus Hound 6.0 5.0 5.0 Bus Hound, IDE SCSI USB 1394 DVD Windows9X,WindowsMe,NT4.0,2000,2003,XP XP IRP Html ZIP SCSI sense USB Bus Hound 1 Bus

More information

TX-NR3030_BAS_Cs_ indd

TX-NR3030_BAS_Cs_ indd TX-NR3030 http://www.onkyo.com/manual/txnr3030/adv/cs.html Cs 1 2 3 Speaker Cable 2 HDMI OUT HDMI IN HDMI OUT HDMI OUT HDMI OUT HDMI OUT 1 DIGITAL OPTICAL OUT AUDIO OUT TV 3 1 5 4 6 1 2 3 3 2 2 4 3 2 5

More information

USING MAYA ANIMATION Keyset set Maya sets partitions MEL MEL copykey cutkey pastekey scalekey snapkey keytangent bakeresults MEL Command Reference Edi

USING MAYA ANIMATION Keyset set Maya sets partitions MEL MEL copykey cutkey pastekey scalekey snapkey keytangent bakeresults MEL Command Reference Edi 9 61 62 65 67 69 69 71 74 76 Maya Edit > Keys > Paste Keys Maya 61 USING MAYA ANIMATION Keyset set Maya sets partitions MEL MEL copykey cutkey pastekey scalekey snapkey keytangent bakeresults MEL Command

More information

清 潔 機 器 人 覆 蓋 率 分 析 之 研 究 A Study of Coverage Analysis for Cleaning Robot 研 究 生 : 林 育 昇 撰 指 導 教 授 : 陳 智 勇 博 士 樹 德 科 技 大 學 電 腦 與 通 訊 研 究 所 碩 士 論 文 A Th

清 潔 機 器 人 覆 蓋 率 分 析 之 研 究 A Study of Coverage Analysis for Cleaning Robot 研 究 生 : 林 育 昇 撰 指 導 教 授 : 陳 智 勇 博 士 樹 德 科 技 大 學 電 腦 與 通 訊 研 究 所 碩 士 論 文 A Th 樹 德 科 技 大 學 電 腦 與 通 訊 研 究 所 碩 士 論 文 清 潔 機 器 人 覆 蓋 率 分 析 之 研 究 A Study of Coverage Analysis for Cleaning Robot 研 究 生 : 林 育 昇 撰 指 導 教 授 : 陳 智 勇 博 士 中 華 民 國 一 百 零 一 年 六 月 清 潔 機 器 人 覆 蓋 率 分 析 之 研 究 A Study

More information

IP TCP/IP PC OS µclinux MPEG4 Blackfin DSP MPEG4 IP UDP Winsock I/O DirectShow Filter DirectShow MPEG4 µclinux TCP/IP IP COM, DirectShow I

IP TCP/IP PC OS µclinux MPEG4 Blackfin DSP MPEG4 IP UDP Winsock I/O DirectShow Filter DirectShow MPEG4 µclinux TCP/IP IP COM, DirectShow I 2004 5 IP TCP/IP PC OS µclinux MPEG4 Blackfin DSP MPEG4 IP UDP Winsock I/O DirectShow Filter DirectShow MPEG4 µclinux TCP/IP IP COM, DirectShow I Abstract The techniques of digital video processing, transferring

More information

C 1

C 1 C homepage: xpzhangme 2018 5 30 C 1 C min(x, y) double C // min c # include # include double min ( double x, double y); int main ( int argc, char * argv []) { double x, y; if( argc!=

More information

17 Prelight Apply Color Paint Vertex Color Tool Prelight Apply Color Paint Vertex Color Tool 242 Apply Color, Prelight Maya Shading Smooth

17 Prelight Apply Color Paint Vertex Color Tool Prelight Apply Color Paint Vertex Color Tool 242 Apply Color, Prelight Maya Shading Smooth 17 Prelight 233 234 242 Apply Color Paint Vertex Color Tool Prelight Apply Color Paint Vertex Color Tool 242 Apply Color, Prelight Maya Shading Smooth Shade All Custom Polygon DisplayOptions Color in Shaded

More information

Text 文字输入功能 , 使用者可自行定义文字 高度, 旋转角度 , 行距 , 字间距离 和 倾斜角度。

Text 文字输入功能 , 使用者可自行定义文字  高度, 旋转角度 , 行距 , 字间距离 和 倾斜角度。 GerbTool Wise Software Solution, Inc. File New OPEN CLOSE Merge SAVE SAVE AS Page Setup Print Print PreView Print setup (,, IMPORT Gerber Wizard Gerber,Aperture Gerber Gerber, RS-274-D, RS-274-X, Fire9000

More information

C/C++ - 数组与指针

C/C++ - 数组与指针 C/C++ Table of contents 1. 2. 3. 4. 5. 6. 7. 8. 1 float candy [ 365]; char code [12]; int states [50]; 2 int array [6] = {1, 2, 4, 6, 8, 10}; 3 // day_mon1.c: # include # define MONTHS 12 int

More information

untitled

untitled Ogre Rendering System http://antsam.blogone.net AntsamCGD@hotmail.com geometry systemmaterial systemshader systemrendering system API API DirectX OpenGL API Pipeline Abstraction API Pipeline Pipeline configurationpipeline

More information

Microsoft PowerPoint - How_To_Accelerate_OpenCV_Applications_using_Vivado_HLS-CF-Andy-v2 [只读]

Microsoft PowerPoint - How_To_Accelerate_OpenCV_Applications_using_Vivado_HLS-CF-Andy-v2 [只读] 如何使用 Vivado HLS 视频库加速 Zynq-7000 All Programmable SoC OpenCV 应用 2013 年 9 月 11 日 OpenCV 简介 开源计算机视觉 (OpenCV) 被广泛用于开发计算机视觉应用 包含 2500 多个优化的视频函数的函数库 专门针对台式机处理器和 GPU 进行优化 用户成千上万 无需修改即可在 Zynq 器件的 ARM 处理器上运行 但是

More information

AN INTRODUCTION TO PHYSICAL COMPUTING USING ARDUINO, GRASSHOPPER, AND FIREFLY (CHINESE EDITION ) INTERACTIVE PROTOTYPING

AN INTRODUCTION TO PHYSICAL COMPUTING USING ARDUINO, GRASSHOPPER, AND FIREFLY (CHINESE EDITION ) INTERACTIVE PROTOTYPING AN INTRODUCTION TO PHYSICAL COMPUTING USING ARDUINO, GRASSHOPPER, AND FIREFLY (CHINESE EDITION ) INTERACTIVE PROTOTYPING 前言 - Andrew Payne 目录 1 2 Firefly Basics 3 COMPONENT TOOLBOX 目录 4 RESOURCES 致谢

More information

51 C 51 isp 10 C PCB C C C C KEIL

51 C 51 isp 10   C   PCB C C C C KEIL http://wwwispdowncom 51 C " + + " 51 AT89S51 In-System-Programming ISP 10 io 244 CPLD ATMEL PIC CPLD/FPGA ARM9 ISP http://wwwispdowncom/showoneproductasp?productid=15 51 C C C C C ispdown http://wwwispdowncom

More information

C/C++ 语言 - 循环

C/C++ 语言 - 循环 C/C++ Table of contents 7. 1. 2. while 3. 4. 5. for 6. 8. (do while) 9. 10. (nested loop) 11. 12. 13. 1 // summing.c: # include int main ( void ) { long num ; long sum = 0L; int status ; printf

More information

新版 明解C++入門編

新版 明解C++入門編 511!... 43, 85!=... 42 "... 118 " "... 337 " "... 8, 290 #... 71 #... 413 #define... 128, 236, 413 #endif... 412 #ifndef... 412 #if... 412 #include... 6, 337 #undef... 413 %... 23, 27 %=... 97 &... 243,

More information

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double

More information

华恒家庭网关方案

华恒家庭网关方案 LINUX V1.5 1 2 1 2 LINUX WINDOWS PC VC LINUX WINDOWS LINUX 90% GUI LINUX C 3 REDHAT 9 LINUX PC TFTP/NFS http://www.hhcn.com/chinese/embedlinux-res.html minicom NFS mount C HHARM9-EDU 1 LINUX HHARM9-EDU

More information

Microsoft PowerPoint - OPVB1基本VB.ppt

Microsoft PowerPoint - OPVB1基本VB.ppt 大 綱 0.VB 能 做 什 麼? CH1 VB 基 本 認 識 1.VB 歷 史 與 版 本 2.VB 環 境 簡 介 3. 即 時 運 算 視 窗 1 0.VB 能 做 什 麼? Visual Basic =>VB=> 程 式 設 計 語 言 => 設 計 程 式 設 計 你 想 要 的 功 能 的 程 式 自 動 化 資 料 庫 計 算 模 擬 遊 戲 網 路 監 控 實 驗 輔 助 自 動

More information

Go构建日请求千亿微服务最佳实践的副本

Go构建日请求千亿微服务最佳实践的副本 Go 构建 请求千亿级微服务实践 项超 100+ 700 万 3000 亿 Goroutine & Channel Goroutine Channel Goroutine func gen() chan int { out := make(chan int) go func(){ for i:=0; i

More information

大漠 伪前端, 就职于淘宝

大漠 伪前端, 就职于淘宝 CSS Grid Layout 2016-12-17 @ 大漠. #CSSConf https://www.flickr.com/photos/19139526@n00/8331063530/ 大漠 伪前端, 就职于淘宝 古老的 table 布局 现代 Web 布局 Float inline-block display: table position (absolute 或 relative)

More information

(Pattern Recognition) 1 1. CCD

(Pattern Recognition) 1 1. CCD ********************************* ********************************* (Pattern Recognition) 1 1. CCD 2. 3. 4. 1 ABSTRACT KeywordsMachine Vision, Real Time Inspection, Image Processing The purpose of this

More information

untitled

untitled MODBUS 1 MODBUS...1 1...4 1.1...4 1.2...4 1.3...4 1.4... 2...5 2.1...5 2.2...5 3...6 3.1 OPENSERIAL...6 3.2 CLOSESERIAL...8 3.3 RDMULTIBIT...8 3.4 RDMULTIWORD...9 3.5 WRTONEBIT...11 3.6 WRTONEWORD...12

More information

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc 2 5 8 11 0 13 1. 13 2. 15 3. 18 1 23 1. 23 2. 26 3. 28 2 36 1. 36 2. 39 3. 42 4. 44 5. 49 6. 51 3 57 1. 57 2. 60 3. 64 4. 66 5. 70 6. 75 7. 83 8. 85 9. 88 10. 98 11. 103 12. 108 13. 112 4 115 1. 115 2.

More information

Microsoft PowerPoint - string_kruse [兼容模式]

Microsoft PowerPoint - string_kruse [兼容模式] Strings Strings in C not encapsulated Every C-string has type char *. Hence, a C-string references an address in memory, the first of a contiguous set of bytes that store the characters making up the string.

More information

三維空間之機械手臂虛擬實境模擬

三維空間之機械手臂虛擬實境模擬 VRML Model of 3-D Robot Arm VRML Model of 3-D Robot Arm MATLAB VRML MATLAB Simulink i MATLAB Simulink V-Realm Build Joystick ii Abstract The major purpose of this thesis presents the procedure of VRML

More information

Microsoft Word - template.doc

Microsoft Word - template.doc HGC efax Service User Guide I. Getting Started Page 1 II. Fax Forward Page 2 4 III. Web Viewing Page 5 7 IV. General Management Page 8 12 V. Help Desk Page 13 VI. Logout Page 13 Page 0 I. Getting Started

More information

3.1 num = 3 ch = 'C' 2

3.1 num = 3 ch = 'C' 2 Java 1 3.1 num = 3 ch = 'C' 2 final 3.1 final : final final double PI=3.1415926; 3 3.2 4 int 3.2 (long int) (int) (short int) (byte) short sum; // sum 5 3.2 Java int long num=32967359818l; C:\java\app3_2.java:6:

More information

概述

概述 OPC Version 1.6 build 0910 KOSRDK Knight OPC Server Rapid Development Toolkits Knight Workgroup, eehoo Technology 2002-9 OPC 1...4 2 API...5 2.1...5 2.2...5 2.2.1 KOS_Init...5 2.2.2 KOS_InitB...5 2.2.3

More information

10384 199928010 UDC 2002 4 2002 6 2002 2002 4 DICOM DICOM 1. 2. 3. Canny 4. 5. DICOM DICOM DICOM DICOM I Abstract Eyes are very important to our lives. Biologic parameters of anterior segment are criterions

More information

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1 C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 月 3 日 1 1 INPUTOUTPUT 1 InputOutput 题目描述 用 cin 输入你的姓名 ( 没有空格 ) 和年龄 ( 整数 ), 并用 cout 输出 输入输出符合以下范例 输入 master 999 输出 I am master, 999 years old. 注意 "," 后面有一个空格,"." 结束,

More information

2 2 3 DLight CPU I/O DLight Oracle Solaris (DTrace) C/C++ Solaris DLight DTrace DLight DLight DLight C C++ Fortran CPU I/O DLight AM

2 2 3 DLight CPU I/O DLight Oracle Solaris (DTrace) C/C++ Solaris DLight DTrace DLight DLight DLight C C++ Fortran CPU I/O DLight AM Oracle Solaris Studio 12.2 DLight 2010 9 2 2 3 DLight 3 3 6 13 CPU 16 18 21 I/O DLight Oracle Solaris (DTrace) C/C++ Solaris DLight DTrace DLight DLight DLight C C++ Fortran CPU I/O DLight AMP Apache MySQL

More information

( CIP) /. :, ( ) ISBN TP CIP ( 2005) : : : : * : : 174 ( A ) : : ( 023) : ( 023)

( CIP) /. :, ( ) ISBN TP CIP ( 2005) : : : : * : : 174 ( A ) : : ( 023) : ( 023) ( CIP) /. :, 2005. 2 ( ) ISBN 7-5624-3339-9.......... TP311. 1 CIP ( 2005) 011794 : : : : * : : 174 ( A ) :400030 : ( 023) 65102378 65105781 : ( 023) 65103686 65105565 : http: / /www. cqup. com. cn : fxk@cqup.

More information

K301Q-D VRT中英文说明书141009

K301Q-D VRT中英文说明书141009 THE INSTALLING INSTRUCTION FOR CONCEALED TANK Important instuction:.. Please confirm the structure and shape before installing the toilet bowl. Meanwhile measure the exact size H between outfall and infall

More information

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1 21 , 7, Windows,,,, : 010-62782989 13501256678 13801310933,,,, ;,, ( CIP) /,,. : ;, 2005. 11 ( 21 ) ISBN 7-81082 - 634-4... - : -. TP316-44 CIP ( 2005) 123583 : : : : 100084 : 010-62776969 : 100044 : 010-51686414

More information

(Guangzhou) AIT Co, Ltd V 110V [ ]! 2

(Guangzhou) AIT Co, Ltd V 110V [ ]! 2 (Guangzhou) AIT Co, Ltd 020-84106666 020-84106688 http://wwwlenxcn Xi III Zebra XI III 1 (Guangzhou) AIT Co, Ltd 020-84106666 020-84106688 http://wwwlenxcn 230V 110V [ ]! 2 (Guangzhou) AIT Co, Ltd 020-84106666

More information

untitled

untitled A, 3+A printf( ABCDEF ) 3+ printf( ABCDEF ) 2.1 C++ main main main) * ( ) ( ) [ ].* ->* ()[] [][] ** *& char (f)(int); ( ) (f) (f) f (int) f int char f char f(int) (f) char (*f)(int); (*f) (int) (

More information

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM CHAPTER 6 SQL SQL SQL 6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM 3. 1986 10 ANSI SQL ANSI X3. 135-1986

More information

Microsoft PowerPoint - ATF2015.ppt [相容模式]

Microsoft PowerPoint - ATF2015.ppt [相容模式] Improving the Video Totalized Method of Stopwatch Calibration Samuel C.K. Ko, Aaron Y.K. Yan and Henry C.K. Ma The Government of Hong Kong Special Administrative Region (SCL) 31 Oct 2015 1 Contents Introduction

More information

Microsoft PowerPoint - ch6 [相容模式]

Microsoft PowerPoint - ch6 [相容模式] UiBinder wzyang@asia.edu.tw UiBinder Java GWT UiBinder XML UI i18n (widget) 1 2 UiBinder HelloWidget.ui.xml: UI HelloWidgetBinder HelloWidget.java XML UI Owner class ( Composite ) UI XML UiBinder: Owner

More information

5-1 nav css 5-2

5-1 nav css 5-2 5 HTML CSS HTML CSS Ê Ê Ê Ê 5-1 nav css 5-2 5-1 5 5-1-1 5-01 css images 01 index.html 02 5-3 style.css css 03 CH5/5-01/images 04 images index.html style.css 05

More information

audiogram3 Owners Manual

audiogram3 Owners Manual USB AUDIO INTERFACE ZH 2 AUDIOGRAM 3 ( ) * Yamaha USB Yamaha USB ( ) ( ) USB Yamaha (5)-10 1/2 AUDIOGRAM 3 3 MIC / INST (XLR ) (IEC60268 ): 1 2 (+) 3 (-) 2 1 3 Yamaha USB Yamaha Yamaha Steinberg Media

More information

晶体结构立体模型建构软件-Diamond的使用

晶体结构立体模型建构软件-Diamond的使用 -Diamond E-mail: wupingwei@mail.ouc.edu.cn -Diamond Diamond NaCl NaCl NaCl Fm-3m(225) a=5.64å Na:4a, Cl:4b 1 2 3 4 5 6 File New OK Diamond1 New Structure Crystal Structure with cell and Spacegroup Cell

More information

影視後製全攻略 Premiere Pro After Effects Encore 自序 Adobe Premiere Pro After Effects Encore 2008 Adobe CS Adobe CS5 Adobe CS4 Premiere Pro After Effect

影視後製全攻略 Premiere Pro After Effects Encore 自序 Adobe Premiere Pro After Effects Encore 2008 Adobe CS Adobe CS5 Adobe CS4 Premiere Pro After Effect 自序 Adobe Premiere Pro After Effects Encore 2008 Adobe CS3 2010 Adobe CS5 Adobe CS4 Premiere Pro After Effects Encore 18 ii Tony Cathy 2010/8 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 iii Premiere

More information

Microsoft Word - 01.DOC

Microsoft Word - 01.DOC 第 1 章 JavaScript 简 介 JavaScript 是 NetScape 公 司 为 Navigator 浏 览 器 开 发 的, 是 写 在 HTML 文 件 中 的 一 种 脚 本 语 言, 能 实 现 网 页 内 容 的 交 互 显 示 当 用 户 在 客 户 端 显 示 该 网 页 时, 浏 览 器 就 会 执 行 JavaScript 程 序, 用 户 通 过 交 互 式 的

More information

untitled

untitled Co-integration and VECM Yi-Nung Yang CYCU, Taiwan May, 2012 不 列 1 Learning objectives Integrated variables Co-integration Vector Error correction model (VECM) Engle-Granger 2-step co-integration test Johansen

More information

MATLAB 1

MATLAB 1 MATLAB 1 MATLAB 2 MATLAB PCI-1711 / PCI-1712 MATLAB PCI-1711 / PCI-1712 MATLAB The Mathworks......1 1...........2 2.......3 3................4 4. DAQ...............5 4.1. DAQ......5 4.2. DAQ......6 5.

More information

Microsoft Word - 11.doc

Microsoft Word - 11.doc 除 錯 技 巧 您 將 於 本 章 學 到 以 下 各 項 : 如 何 在 Visual C++ 2010 的 除 錯 工 具 控 制 下 執 行 程 式? 如 何 逐 步 地 執 行 程 式 的 敘 述? 如 何 監 看 或 改 變 程 式 中 的 變 數 值? 如 何 監 看 程 式 中 計 算 式 的 值? 何 謂 Call Stack? 何 謂 診 斷 器 (assertion)? 如 何

More information

CH01.indd

CH01.indd 3D ios Android Windows 10 App Apple icloud Google Wi-Fi 4G 1 ( 3D ) 2 3 4 5 CPU / / 2 6 App UNIX OS X Windows Linux (ios Android Windows 8/8.1/10 BlackBerry OS) 7 ( ZigBee UWB) (IEEE 802.11/a/b/g/n/ad/ac

More information

Microsoft PowerPoint - Aqua-Sim.pptx

Microsoft PowerPoint - Aqua-Sim.pptx Peng Xie, Zhong Zhou, Zheng Peng, Hai Yan, Tiansi Hu, Jun-Hong Cui, Zhijie Shi, Yunsi Fei, Shengli Zhou Underwater Sensor Network Lab 1 Outline Motivations System Overview Aqua-Sim Components Experimental

More information

<4D6963726F736F667420576F7264202D20A4E2B6D5BFEBC3D1C2B2B3F8BCBDA9F1A874B2CE2E646F63>

<4D6963726F736F667420576F7264202D20A4E2B6D5BFEBC3D1C2B2B3F8BCBDA9F1A874B2CE2E646F63> 致 理 技 術 學 院 資 訊 網 路 技 術 系 實 務 專 題 報 告 致 理 技 術 學 院 資 訊 網 路 技 術 系 實 務 專 題 報 告 手 勢 辨 識 簡 報 播 放 系 統 手 勢 辨 識 簡 報 播 放 系 統 指 導 教 師 : 高 楊 達 老 師 學 生 : 蔡 孟 純 (19634111) 邱 佩 盈 (19634118) 鄭 惠 馨 (19634125) 九 十 九 年

More information

穨2700使用手冊.doc

穨2700使用手冊.doc Keithley 2700 13 CH Avg Ratio continuity Offset Compensation Ohms 80 (differential) 6 (22 ) (Half-rack size) 1000V/3A isolation/input 50000 EEE-488 RS-232 Digital I/O Trigger Link ActiveX Start-up software

More information

untitled

untitled 2006 6 Geoframe Geoframe 4.0.3 Geoframe 1.2 1 Project Manager Project Management Create a new project Create a new project ( ) OK storage setting OK (Create charisma project extension) NO OK 2 Edit project

More information

2782_OME_KM_Cover.qxd

2782_OME_KM_Cover.qxd 数码说明书之家 2005.09.06 www.54gg.com 2 3 4 5 6 7 9 8...14...14...17...18...19...20...20...20...21...22...23...24...24...25...26...28...28...29...29...30...32...32 EVF LCD...32...33...34...34...35...35...36...36...37...38...39...40...40...41...41...42...43...44...45...45...46...47...48...49...50...50

More information

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double x) { d

More information

VB程序设计教程

VB程序设计教程 高 等 学 校 教 材 Visual Basic 程 序 设 计 教 程 魏 东 平 郑 立 垠 梁 玉 环 石 油 大 学 出 版 社 内 容 提 要 本 书 是 按 高 等 学 校 计 算 机 程 序 设 计 课 程 教 学 大 纲 编 写 的 大 学 教 材, 主 要 包 括 VB 基 础 知 识 常 用 程 序 结 构 和 算 法 Windows 用 户 界 面 设 计 基 础 文 件 处

More information

Microsoft Word - scribe_1_.doc

Microsoft Word - scribe_1_.doc Making Faces 2005/06/08 R93922063 陳 坤 毅 R93922087 莊 曜 誠 R93922105 王 博 民 3D acquisition for faces: 如 何 取 得 臉 的 3D model? 方 法 1: ( 經 費 足 夠 時 ) 使 用 Cyberware scanner. ( 可 對 臉 部 scan 亦 可 對 全 身 scan) 方 法 如

More information

1 目 錄 1. 簡 介... 2 2. 一 般 甄 試 程 序... 2 3. 第 一 階 段 的 準 備... 5 4. 第 二 階 段 的 準 備... 9 5. 每 間 學 校 的 面 試 方 式... 11 6. 各 程 序 我 的 做 法 心 得 及 筆 記... 13 7. 結 論..

1 目 錄 1. 簡 介... 2 2. 一 般 甄 試 程 序... 2 3. 第 一 階 段 的 準 備... 5 4. 第 二 階 段 的 準 備... 9 5. 每 間 學 校 的 面 試 方 式... 11 6. 各 程 序 我 的 做 法 心 得 及 筆 記... 13 7. 結 論.. 如 何 準 備 研 究 所 甄 試 劉 富 翃 1 目 錄 1. 簡 介... 2 2. 一 般 甄 試 程 序... 2 3. 第 一 階 段 的 準 備... 5 4. 第 二 階 段 的 準 備... 9 5. 每 間 學 校 的 面 試 方 式... 11 6. 各 程 序 我 的 做 法 心 得 及 筆 記... 13 7. 結 論... 20 8. 附 錄 8.1 推 甄 書 面 資 料...

More information

C/C++语言 - C/C++数据

C/C++语言 - C/C++数据 C/C++ C/C++ Table of contents 1. 2. 3. 4. char 5. 1 C = 5 (F 32). 9 F C 2 1 // fal2cel. c: Convert Fah temperature to Cel temperature 2 # include < stdio.h> 3 int main ( void ) 4 { 5 float fah, cel ;

More information

Improved Preimage Attacks on AES-like Hash Functions: Applications to Whirlpool and Grøstl

Improved Preimage Attacks on AES-like Hash Functions: Applications to Whirlpool and Grøstl SKLOIS (Pseudo) Preimage Attack on Reduced-Round Grøstl Hash Function and Others Shuang Wu, Dengguo Feng, Wenling Wu, Jian Guo, Le Dong, Jian Zou March 20, 2012 Institute. of Software, Chinese Academy

More information

提纲 1 2 OS Examples for 3

提纲 1 2 OS Examples for 3 第 4 章 Threads2( 线程 2) 中国科学技术大学计算机学院 October 28, 2009 提纲 1 2 OS Examples for 3 Outline 1 2 OS Examples for 3 Windows XP Threads I An Windows XP application runs as a seperate process, and each process may

More information

Cube20S small, speedy, safe Eextremely modular Up to 64 modules per bus node Quick reaction time: up to 20 µs Cube20S A new Member of the Cube Family

Cube20S small, speedy, safe Eextremely modular Up to 64 modules per bus node Quick reaction time: up to 20 µs Cube20S A new Member of the Cube Family small, speedy, safe Eextremely modular Up to 64 modules per bus de Quick reaction time: up to 20 µs A new Member of the Cube Family Murrelektronik s modular I/O system expands the field-tested Cube family

More information

p.2 1 <HTML> 2 3 <HEAD> 4 <TITLE> </TITLE> 5 </HEAD> 6 7 <BODY> 8 <H3><B> </B></H3> 9 <H4><I> </I></H4> 10 </BODY> </HTML> 1. HTML 1. 2.

p.2 1 <HTML> 2 3 <HEAD> 4 <TITLE> </TITLE> 5 </HEAD> 6 7 <BODY> 8 <H3><B> </B></H3> 9 <H4><I> </I></H4> 10 </BODY> </HTML> 1. HTML 1. 2. 2005-06 p.1 HTML HyperText Mark-up Language 1. HTML Logo, Pascal, C++, Java HTML 2. HTML (tag) 3. HTML 4. HTML 1. HTML 2. 3. FTP HTML HTML html 1. html html html cutehtmleasyhtml 2. wyswyg (What you see

More information

els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8

els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8 els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8 Yamaha ELS-0/0C..8 LCD ELS-0/0C v. typeu LCD ELS-0/0C typeu / -6 / [SEARCH] / - ZH ELS-0/0C.8 els0xu_zh_nf_v8.book Page Wednesday, June,

More information

1.ai

1.ai HDMI camera ARTRAY CO,. LTD Introduction Thank you for purchasing the ARTCAM HDMI camera series. This manual shows the direction how to use the viewer software. Please refer other instructions or contact

More information

epub 33-8

epub 33-8 8 1) 2) 3) A S C I I 4 C I / O I / 8.1 8.1.1 1. ANSI C F I L E s t d i o. h typedef struct i n t _ f d ; i n t _ c l e f t ; i n t _ m o d e ; c h a r *_ n e x t ; char *_buff; /* /* /* /* /* 1 5 4 C FILE

More information

Outline Speech Signals Processing Dual-Tone Multifrequency Signal Detection 云南大学滇池学院课程 : 数字信号处理 Applications of Digital Signal Processing 2

Outline Speech Signals Processing Dual-Tone Multifrequency Signal Detection 云南大学滇池学院课程 : 数字信号处理 Applications of Digital Signal Processing 2 CHAPTER 10 Applications of Digital Signal Processing Wang Weilian wlwang@ynu.edu.cn School of Information Science and Technology Yunnan University Outline Speech Signals Processing Dual-Tone Multifrequency

More information

untitled

untitled 3 C++ 3.1 3.2 3.3 3.4 new delete 3.5 this 3.6 3.7 3.1 3.1 class struct union struct union C class C++ C++ 3.1 3.1 #include struct STRING { typedef char *CHARPTR; // CHARPTR s; // int strlen(

More information

2004cm

2004cm 重要注意事項 視窗系統所在的硬碟分割區 ( 一般常使用 C:\ 硬碟分割區 ), 建議最少要有 20 GB 的可使用空間, 且該硬碟分割區內只限於安裝視窗系統及 DVR 主機程式 錄影紀錄的儲存位置, 應該避免使用視窗系統所在的硬碟分割區, 而是在其他的硬碟分割區內 這樣的配置方式能保持視窗系統及 DVR 主機程式的執行效能及長期穩定性 2400 1 2 2404S H1004S - - - 2416SG

More information

States and capital package

States and capital package : 1 Students are required to know 50 states and capitals and their geological locations. This is an independent working packet to learn about 50 states and capital. Students are asked to fulfill 4 activities

More information

untitled

untitled 料 2-1 料 料 x, y, z 料 不 不 料濾 料 不 料 料 不 料 錄 料 2-1 a 料 2-1 b 2003 a 料 b 料 2-1 料 2003 料 料 行 料濾 料亂 濾 料 料 滑 料 理 料 2001 料 兩 理 料 不 TIN, Triangular Irregular Network 8 2-2 a 數 量 料 便 精 2003 料 行 理 料 立 狀 連 料 狀 立 料

More information

untitled

untitled 不 料 料 例 : ( 料 ) 串 度 8 年 數 串 度 4 串 度 數 數 9- ( ) 利 數 struct { ; ; 數 struct 數 ; 9-2 數 利 數 C struct 數 ; C++ 數 ; struct 省略 9-3 例 ( 料 例 ) struct people{ char name[]; int age; char address[4]; char phone[]; int

More information

1 Framework.NET Framework Microsoft Windows.NET Framework.NET Framework NOTE.NET NET Framework.NET Framework 2.0 ( 3 ).NET Framework 2.0.NET F

1 Framework.NET Framework Microsoft Windows.NET Framework.NET Framework NOTE.NET NET Framework.NET Framework 2.0 ( 3 ).NET Framework 2.0.NET F 1 Framework.NET Framework Microsoft Windows.NET Framework.NET Framework NOTE.NET 2.0 2.0.NET Framework.NET Framework 2.0 ( 3).NET Framework 2.0.NET Framework ( System ) o o o o o o Boxing UnBoxing() o

More information

Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies to all d

Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies to all d WeChat Search Visual Identity Guidelines WEDESIGN 2018. 04 Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies

More information