HUNNISH 注:
本翻译是直接根据 OpenCV Beta 4.0 版本的用户手册翻译的,原文件是:<opencv_directory>/doc/ref/opencvref_cv.htm, 可以从 SOURCEFORG 上面的 OpenCV 项目下载,也可以直接从 阿须数码 中下载:http://www.assuredigit.com/incoming/sourcecode/opencv/chinese_docs/ref/opencvref_cv.htm。
翻译中肯定有不少错误,另外也有些术语和原文语义理解不透导致翻译不准确或者错误,也请有心人赐教。
图像处理、结构分析、运动分析和对象跟踪部分由R.Z.LIU翻译,模式识别、照相机定标与三维重建部分由H.M.ZHANG翻译,全文由Y.C.WEI统一修改校正。
注意:
本章描述图像处理和分析的一些函数。其中大多数函数都是针对两维象素数组的,这里,我们称这些数组为“图像”,但是它们不一定非得是IplImage 结构,也可以是CvMat或者CvMatND结构。
使用扩展 Sobel 算子计算一阶、二阶、三阶或混合图像差分
void cvSobel( const CvArr* src, CvArr* dst, int xorder, int yorder, int aperture_size=3 );
| -3 0 3| |-10 0 10| | -3 0 3|对 x-方向 以及转置矩阵对 y-方向。
函数 cvSobel 通过对图像用相应的内核进行卷积操作来计算图像差分:
dst(x,y) = dxorder+yodersrc/dxxorder•dyyorder |(x,y)由于Sobel 算子结合了 Gaussian 平滑和微分,所以,其结果或多或少对噪声有一定的鲁棒性。通常情况,函数调用采用如下参数 (xorder=1, yorder=0, aperture_size=3) 或 (xorder=0, yorder=1, aperture_size=3) 来计算一阶 x- 或 y- 方向的图像差分。第一种情况对应:
|-1 0 1| |-2 0 2| |-1 0 1|
核。第二种对应
|-1 -2 -1| | 0 0 0| | 1 2 1| or | 1 2 1| | 0 0 0| |-1 -2 -1|核的选则依赖于图像原点的定义 (
origin
来自
IplImage
结构的定义)。由于该函数不进行图像尺度变换,所以和输入图像(数组)相比,输出图像(数组)的元素通常具有更大的绝对数值(译者注:即象素的深度)。为防止溢出,当输入图像是 8 位的,要求输出图像是 16 位的。当然可以用函数函数 cvConvertScale
或 cvConvertScaleAbs
转换为 8 位的。除了 8-比特 图像,函数也接受 32-位 浮点数图像。所有输入和输出图像都必须是单通道的,并且具有相同的图像尺寸或者ROI尺寸。
计算图像的 Laplacian 变换
void cvLaplace( const CvArr* src, CvArr* dst, int aperture_size=3 );
函数 cvLaplace 计算输入图像的 Laplacian变换,方法是先用 sobel 算子计算二阶 x- 和 y- 差分,再求和:
dst(x,y) = d2src/dx2 + d2src/dy2
对 aperture_size
=1 则给出最快计算结果,相当于对图像采用如下内核做卷积:
|0 1 0| |1 -4 1| |0 1 0|
类似于 cvSobel 函数,该函数也不作图像的尺度变换,所支持的输入、输出图像类型的组合和cvSobel一致。
采用 Canny 算法做边缘检测
void cvCanny( const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3 );
函数 cvCanny 采用 CANNY 算法发现输入图像的边缘而且在输出图像中标识这些边缘。threshold1
和threshold2
当中的小 阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割。
计算用于角点检测的特征图,
void cvPreCornerDetect( const CvArr* image, CvArr* corners, int aperture_size=3 );
函数 cvPreCornerDetect 计算函数 Dx2Dyy+Dy2Dxx - 2DxDyDxy 其中 D? 表示一阶图像差分,D?? 表示二阶图像差分。 角点被认为是函数的局部最大值:
// 假设图像格式为浮点数 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 );
计算图像块的特征值和特征向量,用于角点检测
void cvCornerEigenValsAndVecs( const CvArr* image, CvArr* eigenvv, int block_size, int aperture_size=3 );
对每个象素,函数 cvCornerEigenValsAndVecs 考虑 block_size
× block_size
大小的邻域 S(p),然后在邻域上计算图像差分的相关矩阵:
| sumS(p)(dI/dx)2 sumS(p)(dI/dx•dI/dy)| M = | | | sumS(p)(dI/dx•dI/dy) sumS(p)(dI/dy)2 |
然后它计算矩阵的特征值和特征向量,并且按如下方式(λ1, λ2, x1, y1, x2, y2)存储这些值到输出图像中,其中
λ1, λ2 - M 的
特征值,没有排序
(x1, y1) - 特征向量,对 λ1
(x2, y2) - 特征向量,对 λ2
计算梯度矩阵的最小特征值,用于角点检测
void cvCornerMinEigenVal( const CvArr* image, CvArr* eigenval, int block_size, int aperture_size=3 );
函数 cvCornerMinEigenVal 与 cvCornerEigenValsAndVecs 类似,但是它仅仅计算和存储每个象素点差分相关矩阵的最小特征值,即前一个函数的 min(λ1, λ2)
精确角点位置
void cvFindCornerSubPix( const CvArr* image, CvPoint2D32f* corners, int count, CvSize win, CvSize zero_zone, CvTermCriteria criteria );
win
=(5,5) 那么使用 5*2+1 × 5*2+1 = 11 × 11 大小的搜索窗口
criteria
可以是最大迭代数目,或者是设定的精确度,也可以是它们的组合。
函数 cvFindCornerSubPix 通过迭代来发现具有子象素精度的角点位置,或如图所示的放射鞍点(radial saddle points)。
子象素级角点定位的实现是基于对向量正交性的观测而实现的,即从中央点q
到其邻域点p
的向量 和p
点处的图像梯度正交(服从图像和测量噪声)。考虑以下的表达式:
εi=DIpiT•(q-pi)其中,
DIpi
表示在
q
的一个邻域点
pi
处的图像梯度,
q
的值通过最小化
εi
得到。通过将
εi
设为0,可以建立系统方程如下:
sumi(DIpi•DIpiT)•q - sumi(DIpi•DIpiT•pi) = 0
其中q
的邻域(搜索窗)中的梯度被累加。调用第一个梯度参数G
和第二个梯度参数b
,得到:
q=G-1•b
该算法将搜索窗的中心设为新的中心q
,然后迭代,直到找到低于某个阈值点的中心位置。
确定图像的强角点
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 );
eig_image
一致
函数 cvGoodFeaturesToTrack 在图像中寻找具有大特征值的角点。该函数,首先用cvCornerMinEigenVal 计算输入图像的每一个象素点的最小特征值,并将结果存储到变量 eig_image 中。
然后进行非最大值抑制(仅保留3x3邻域中的局部最大值)。下一步将最小特征值小于quality_level
•max(eig_image
(x,y)) 排除掉。最后,函数确保所有发现的角点之间具有足够的距离,(最强的角点第一个保留,然后检查新的角点与已有角点之间的距离大于 min_distance )。
初始化线段迭代器
int cvInitLineIterator( const CvArr* image, CvPoint pt1, CvPoint pt2, CvLineIterator* line_iterator, int connectivity=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); /* 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 ); }
将光栅线读入缓冲区
int cvSampleLine( const CvArr* image, CvPoint pt1, CvPoint pt2, void* buffer, int connectivity=8 );
pt2.x
-
pt1.x
|+1, |
pt2.y
-
pt1.y
|+1 ) :8-连通情况下,或者 |
pt2.x
-
pt1.x
|+|
pt2.y
-
pt1.y
|+1 : 4-连通情况下.
函数 cvSampleLine 实现了线段迭代器的一个特殊应用。它读取由两点 pt1 和 pt2 确定的线段上的所有图像点,包括终点,并存储到缓存中。
从图像中提取象素矩形,使用子象素精度
void cvGetRectSubPix( const CvArr* src, CvArr* dst, CvPoint2D32f center );
函数 cvGetRectSubPix 从图像 src 中提取矩形
:
dst(x, y) = src(x + center.x - (width(dst)-1)*0.5, y + center.y - (height(dst)-1)*0.5)
其中非整数象素点坐标采用双线性差值提取。对多通道图像,每个通道独立单独完成提取。尽管函数要求矩形的中心一定要在输入图像之中,但是有可能出现矩形的一部分超出图像边界的情况,这时,该函数复制边界的模识(hunnish:即用于矩形相交的图像边界线段的象素来代替矩形超越部分的象素)。
提取象素四边形,使用子象素精度
void cvGetQuadrangleSubPix( const CvArr* src, CvArr* dst, const CvMat* map_matrix, int fill_outliers=0, CvScalar fill_value=cvScalarAll(0) );
A
|
b
] (见讨论).
fill_outliers
=0)进行差值或者将其设置为指定值(
fill_outliers
=1)。
fill_outliers
=1时的情况).
函数 cvGetQuadrangleSubPix 以子象素精度从图像 src
中提取四边形,使用子象素精度,并且将结果存储于 dst
,计算公式是:
dst(x+width(dst)/2, y+height(dst)/2)= src( A11x+A12y+b1, A21x+A22y+b2), w其中A
和b
均来自映射矩阵(译者注:A, b为几何形变参数)map_matrix
| A11 A12 b1 | map_matrix = | | | A21 A22 b2 |
其中在非整数坐标 A•(x,y)T+b 的象素点值通过双线性变换得到。多通道图像的每一个通道都单独计算.
#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 ) break; angle = (angle + delta) % 360; } } return 0; }
图像大小变换
void cvResize( const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR );
CV_INTER_NN
方法..函数 cvResize 将图像 src
改变尺寸得到与 dst 同样大小。
若设定 ROI,函数将按常规支持 ROI.
对图像做仿射变换
void cvWarpAffine( const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0) );
fillval
.matrix
是输出图像到输入图像的反变换,因此可以直接用来做象素差值。否则, 函数从map_matrix 得到反变换。
函数 cvWarpAffine 利用下面指定的矩阵变换输入图像:
dst(x',y')<-src(x,y) 如果没有指定 CV_WARP_INVERSE_MAP , (x',y')T=map_matrix•(x,y,1)T+b , 否则, (x, y)T=map_matrix•(x',y&apos,1)T+b
函数与 cvGetQuadrangleSubPix 类似,但是不完全相同。 cvWarpAffine 要求输入和输出图像具有同样的数据类型,有更大的资源开销(因此对小图像不太合适)而且输出图像的部分可以保留不变。而 cvGetQuadrangleSubPix 可以精确地从8位图像中提取四边形到浮点数缓存区中,具有比较小的系统开销,而且总是全部改变输出图像的内容。
要变换稀疏矩阵,使用 cxcore 中的函数 cvTransform 。
计算二维旋转的仿射变换矩阵
CvMat* cv2DRotationMatrix( CvPoint2D32f center, double angle, double scale, CvMat* map_matrix );
函数 cv2DRotationMatrix 计算矩阵:
[ α β | (1-α)*center.x - β*center.y ] [ -β α | β*center.x + (1-α)*center.y ] where α=scale*cos(angle), β=scale*sin(angle)
该变换并不改变原始旋转中心点的坐标,如果这不是操作目的,则可以通过调整平移量改变其坐标(译者注:通过简单的推导可知,放射变换的实现是首先将旋转中心置为坐标原点,再进行旋转和尺度变换,最后重新将坐标原点设定为输入图像的左上角,这里的平移量是center.x, center.y).
对图像进行透视变换
void cvWarpPerspective( const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0) );
fillval
.matrix
是输出图像到输入图像的反变换,因此可以直接用来做象素差值。否则, 函数从map_matrix 得到反变换。
函数 cvWarpPerspective 利用下面指定矩阵变换输入图像:
dst(x',y')<-src(x,y) 若指定 CV_WARP_INVERSE_MAP, (tx',ty',t)T=map_matrix•(x,y,1)T+b 否则, (tx, ty, t)T=map_matrix•(x',y&apos,1)T+b
要变换稀疏矩阵,使用 cxcore 中的函数 cvTransform 。
用4个对应点计算透视变换矩阵
CvMat* cvWarpPerspectiveQMatrix( const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* map_matrix );
函数 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
.
创建结构元素
IplConvKernel* cvCreateStructuringElementEx( int cols, int rows, int anchor_x, int anchor_y, int shape, int* values=NULL );
CV_SHAPE_RECT
, 长方形元素;CV_SHAPE_CROSS
, 交错元素 a cross-shaped element;CV_SHAPE_ELLIPSE
, 椭圆元素;CV_SHAPE_CUSTOM
, 用户自定义元素。这种情况下参数 values
定义了 mask,即象素的那个邻域必须考虑。CV_SHAPE_CUSTOM
时才予以考虑)。
函数 cv CreateStructuringElementEx 分配和填充结构 IplConvKernel
, 它可作为形态操作中的结构元素。
删除结构元素
void cvReleaseStructuringElement( IplConvKernel** element );
函数 cvReleaseStructuringElement 释放结构 IplConvKernel 。
如果 *element
为 NULL
, 则函数不作用。
使用任意结构元素腐蚀图像
void cvErode( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );
NULL
, 则使用 3×3 长方形的结构元素
函数 cvErode 对输入图像使用指定的结构元素进行腐蚀,该结构元素决定每个具有最小值象素点的邻域形状:
dst=erode(src,element): dst(x,y)=min((x',y') in element))src(x+x',y+y')
函数可能是本地操作,不需另外开辟存储空间的意思。腐蚀可以重复进行 (iterations
) 次. 对彩色图像,每个彩色通道单独处理。
使用任意结构元素膨胀图像
void cvDilate( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );
NULL
, 则使用 3×3 长方形的结构元素
函数 cvDilate 对输入图像使用指定的结构元进行膨胀,该结构决定每个具有最小值象素点的邻域形状:
dst=dilate(src,element): dst(x,y)=max((x',y') in element))src(x+x',y+y')
函数支持(in-place)模式。膨胀可以重复进行 (iterations
) 次. 对彩色图像,每个彩色通道单独处理。
高级形态学变换
void cvMorphologyEx( const CvArr* src, CvArr* dst, CvArr* temp, IplConvKernel* element, int operation, int iterations=1 );
CV_MOP_OPEN
- 开运算
CV_MOP_CLOSE
- 闭运算
CV_MOP_GRADIENT
- 形态梯度
CV_MOP_TOPHAT
- "顶帽"
CV_MOP_BLACKHAT
- "黑帽"
函数 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 模式下需要。
各种方法的图像平滑
void cvSmooth( const CvArr* src, CvArr* dst, int smoothtype=CV_GAUSSIAN, int param1=3, int param2=0, double param3=0 );
param1
×param2
领域求和。如果邻域大小是变化的,可以事先利用函数 cvIntegral 计算积分图像。param1
×param2
邻域
求和并做尺度变换 1/(param1
•param2
).param1
×param2
的高斯卷积param1
×param1
的中值滤波 (i.e. 邻域是方的).param1
,空间 sigma=param2
. 关于双向滤波,可参考http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.htmlparam2的值
为零,则表示其被设定为
param1。
sigma = (n/2 - 1)*0.3 + 0.8, 其中 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 方式处理图像.
对图像做卷积
void cvFilter2D( const CvArr* src, CvArr* dst, const CvMat* kernel, CvPoint anchor=cvPoint(-1,-1)); #define cvConvolve2D cvFilter2D
函数 cvFilter2D 对图像进行线性滤波,支持 In-place 操作。当核运算部分超出输入图像时,函数从最近邻的图像内部象素差值得到边界外面的象素值。
计算积分图像
void cvIntegral( const CvArr* image, CvArr* sum, CvArr* sqsum=NULL, CvArr* tilted_sum=NULL );
W
×
H
, 单通道,8位或浮点 (32f 或 64f).
W+1
×
H+1(译者注:原文的公式应该写成(W+1)
×
(H+1),避免误会)
, 单通道,32位整数或 double 精度的浮点数(64f).
W+1
×
H+1(译者注:原文的公式应该写成(W+1)
×
(H+1),避免误会)
, 单通道,32位整数或 double 精度的浮点数 (64f).
函数 cvIntegral 计算一次或高次积分图像:
sum(X,Y)=sumx<X,y<Yimage(x,y) sqsum(X,Y)=sumx<X,y<Yimage(x,y)2 tilted_sum(X,Y)=sumy<Y,abs(x-X)<yimage(x,y)
利用积分图像,可以计算在某象素的上-右方的或者旋转的矩形区域中进行求和、求均值以及标准方差的计算,并且保证运算的复杂度为O(1)。例如:
sumx1<=x<x2,y1<=y<y2image(x,y)=sum(x2,y2)-sum(x1,y2)-sum(x2,y1)+sum(x1,x1)
因此可以在变化的窗口内做快速平滑或窗口相关等操作。
色彩空间转换
void cvCvtColor( const CvArr* src, CvArr* dst, int code );
函数 cvCvtColor 将输入图像从一个色彩空间转换为另外一个色彩空间。函数忽略 IplImage
头中定义的 colorModel
和 channelSeq
域,所以输入图像的色彩空间应该正确指定 (包括通道的顺序,对RGB空间而言,BGR 意味着布局为 B0 G0 R0 B1 G1 R1 ... 层叠的 24-位格式,而 RGB 意味着布局为 R0 G0 B0 R1 G1 B1 ... 层叠的24-位格式. 函数做如下变换:
RGB[A]->Gray: Y=0.212671*R + 0.715160*G + 0.072169*B + 0*A Gray->RGB[A]: R=Y G=Y B=Y A=0
所有可能的图像色彩空间的相互变换公式列举如下:
|X| |0.412411 0.357585 0.180454| |R| |Y| = |0.212649 0.715169 0.072182|*|G| |Z| |0.019332 0.119195 0.950390| |B| |R| | 3.240479 -1.53715 -0.498535| |X| |G| = |-0.969256 1.875991 0.041556|*|Y| |B| | 0.055648 -0.204043 1.057311| |Z|
Y=0.299*R + 0.587*G + 0.114*B Cr=(R-Y)*0.713 + 128 Cb=(B-Y)*0.564 + 128 R=Y + 1.403*(Cr - 128) G=Y - 0.344*(Cr - 128) - 0.714*(Cb - 128) B=Y + 1.773*(Cb - 128)
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位。
|X| |0.433910 0.376220 0.189860| |R/255| |Y| = |0.212649 0.715169 0.072182|*|G/255| |Z| |0.017756 0.109478 0.872915| |B/255| L = 116*Y1/3 for Y>0.008856 L = 903.3*Y for Y<=0.008856 a = 500*(f(X)-f(Y)) b = 200*(f(Y)-f(Z)) where f(t)=t1/3 for t>0.008856 f(t)=7.787*t+16/116 for t<=0.008856上面的公式可以参考 http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html
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_BayerC1C22{RGB|RGB}中的两个字母C1和C2表示特定的模式类型:颜色分量分别来自于第二行,第二和第三列。比如说,上述的模式具有很流行的"BG"类型。
对数组元素进行固定阈值操作
void cvThreshold( const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type );
src
的类型一致,或者为 8-比特.
CV_THRESH_BINARY
和
CV_THRESH_BINARY_INV
的最大值.
函数 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
下面是图形化的阈值描述:
自适应阈值方法
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 );
CV_THRESH_BINARY
和
CV_THRESH_BINARY_INV
的最大值.
CV_ADAPTIVE_THRESH_MEAN_C
或
CV_ADAPTIVE_THRESH_GAUSSIAN_C
(见讨论).
CV_THRESH_BINARY,
CV_THRESH_BINARY_INV
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。
图像的下采样
void cvPyrDown( const CvArr* src, CvArr* dst, int filter=CV_GAUSSIAN_5x5 );
CV_GAUSSIAN_5x5
函数 cvPyrDown 使用 Gaussian 金字塔分解对输入图像向下采样。首先它对输入图像用指定滤波器进行卷积,然后通过拒绝偶数的行与列来下采样图像。
图像的上采样
void cvPyrUp( const CvArr* src, CvArr* dst, int filter=CV_GAUSSIAN_5x5 );
CV_GAUSSIAN_5x5
函数 cvPyrUp 使用Gaussian 金字塔分解对输入图像向上采样。首先通过在图像中插入 0 偶数行和偶数列,然后对得到的图像用指定的滤波器进行高斯卷积,其中滤波器乘以4做差值。所以输出图像是输入图像的 4 倍大小。(hunnish: 原理不清楚,尚待探讨)
用金字塔实现图像分割
void cvPyrSegmentation( IplImage* src, IplImage* dst, CvMemStorage* storage, CvSeq** comp, int level, double threshold1, double 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-通道图像,且大小一样
连接部件
typedef struct CvConnectedComp { double area; /* 连通域的面积 */ float value; /* 分割域的灰度缩放值 */ CvRect rect; /* 分割域的 ROI */ } CvConnectedComp;
用指定颜色填充一个连接域
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)
当前观察象素值与其部件领域象素或者待加入该部件的种子象素之负差(Lower difference)的最大值。对 8-比特 彩色图像,它是一个 packed value.
new_val
), 但填充面具图像 (这种情况下 MASK 必须是非空的).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, 彩色图像,固定范围其中
src(x',y')
是象素邻域点的值。也就是说,为了被加入到连通域中,一个象素的彩色/亮度应该足够接近于:
在二值图像中寻找轮廓
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) );
method
=CV_CHAIN_CODE,则序列头的大小 >=sizeof( CvChain),否则 >=sizeof(CvContour) .
CV_RETR_EXTERNAL
- 只提取最外层的轮廓CV_RETR_LIST
- 提取所有轮廓,并且放置在 list 中CV_RETR_CCOMP
- 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。CV_RETR_TREE
- 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy CV_RETR_RUNS
).
CV_CHAIN_CODE
- Freeman 链码的输出轮廓. 其它方法输出多边形(定点序列).CV_CHAIN_APPROX_NONE
- 将所有点由链码形式翻译为点序列形式CV_CHAIN_APPROX_SIMPLE
- 压缩水平、垂直和对角分割,即函数只保留末端的象素点;CV_CHAIN_APPROX_TC89_L1,
CV_CHAIN_APPROX_TC89_KCOS
- 应用 Teh-Chin 链逼近算法.
CV_LINK_RUNS
- 通过连接为 1 的水平碎片使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST
提取模式可以在本方法中应用.函数 cvFindContours 从二值图像中提取轮廓,并且返回提取轮廓的数目。指针 first_contour
的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为 NULL,则没有检测到轮廓(比如图像是全黑的)。其它轮廓可以从 first_contour 利用
h_next
和 v_next
链接访问到。 在 cvDrawContours 的样例显示如何使用轮廓来进行连通域的检测。轮廓也可以用来做形状分析和对象识别 - 见CVPR2001 教程中的 squares
样例。该教程可以在 SourceForge 网站上找到。
初始化轮廓的扫描过程
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) );
method
=CV_CHAIN_CODE,否则尺寸 >=sizeof(CvContour) .
函数 cvStartFindContours 初始化并且返回轮廓扫描器的指针。扫描器在 cvFindNextContour 使用以提取其余的轮廓。
Finds next contour in the image
CvSeq* cvFindNextContour( CvContourScanner scanner );
函数 cvFindNextContour 确定和提取图像的下一个轮廓,并且返回它的指针。若没有更多的轮廓,则函数返回 NULL.
替换提取的轮廓
void cvSubstituteContour( CvContourScanner scanner, CvSeq* new_contour );
函数 cvSubstituteContour 把用户自定义的轮廓替换前一次的函数 cvFindNextContour 调用所提取的轮廓,该轮廓以用户定义的模式存储在边缘扫描状态之中。轮廓,根据提取状态,被插入到生成的结构,List,二层 hierarchy, 或 tree 中。如果参数new_contour
=NULL, 则提取的轮廓不被包含入生成结构中,它的所有后代以后也不会被加入到接口中。
结束扫描过程
CvSeq* cvEndFindContours( CvContourScanner* scanner );
函数 cvEndFindContours 结束扫描过程,并且返回最高层的第一个轮廓的指针。
计算多边形和光栅形状的最高达三阶的所有矩
void cvMoments( const CvArr* arr, CvMoments* moments, int binary=0 );
函数 cvMoments 计算最高达三阶的空间和中心矩,并且将结果存在结构 moments
中。矩用来计算形状的重心,面积,主轴和其它的形状特征,如 7 Hu 不变量等。
从矩状态结构中提取空间矩
double cvGetSpatialMoment( CvMoments* moments, int x_order, int y_order );
x_order
>= 0.
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)
的亮度值.
从矩状态结构中提取中心矩
double cvGetCentralMoment( CvMoments* moments, int x_order, int y_order );
x_order
>= 0.
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
- 重心坐标
从矩状态结构中提取归一化的中心矩
double cvGetNormalizedCentralMoment( CvMoments* moments, int x_order, int y_order );
x_order
>= 0.
y_order
>= 0 且
x_order
+
y_order
<= 3.
函数 cvGetNormalizedCentralMoment 提取归一化中心矩:
ηx_order,y_order= μx_order,y_order/M00((y_order+x_order)/2+1)
计算 7 Hu 不变量
void cvGetHuMoments( CvMoments* moments, CvHuMoments* hu_moments );
函数 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个除外,因为它的符合会因为反射而改变。
利用 Hough 变换在二值图像中找到直线
CvSeq* cvHoughLines2( CvArr* image, void* line_storage, int method, double rho, double theta, int threshold, double param1=0, double param2=0 );
cols
/
rows
将包含一组检测到的线段。如果
line_storage
是矩阵,而实际线段的数目超过矩阵尺寸,那么最大可能数目的线段被返回(线段没有按照长度、可信度或其它指标排序).
CV_HOUGH_STANDARD
- 传统或标准 Hough 变换. 每一个线段由两个浮点数 (ρ, θ) 表示,其中 ρ 是点与原点 (0,0) 之间的距离,θ 线段与 x-轴之间的夹角。因此,矩阵类型必须是 CV_32FC2 type.CV_HOUGH_PROBABILISTIC
- 概率 Hough 变换(如果图像包含一些长的线性分割,则效率更高). 它返回线段分割而不是整个线段。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是 CV_32SC4.CV_HOUGH_MULTI_SCALE
- 传统 Hough 变换的多尺度变种。线段的编码方式与 CV_HOUGH_STANDARD 的一致。threshold, 则
函数返回的这个线段.
rho
的分母 (大致的距离精度是 rho
而精确的应该是 rho
/ param1
).theta
的分母 (大致的角度精度是 theta
而精确的角度应该是 theta
/ param2
).函数 cvHoughLines2 实现了用于线段检测的不同 Hough 变换方法.
/* This is a standalone program. Pass an image name as a first parameter 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; cvCanny( src, 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) < 0.001 ) { pt1.x = pt2.x = cvRound(rho); pt1.y = 0; pt2.y = color_dst->height; } else if( fabs(b) < 0.001 ) { 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); } }
这是函数所用的样本图像:
下面是程序的输出,采用概率 Hough transform ("#if 0" 的部分):
计算输入图像的所有非零元素对其最近零元素的距离
void cvDistTransform( const CvArr* src, CvArr* dst, int distance_type=CV_DIST_L2, int mask_size=3, const float* mask=NULL );
CV_DIST_L1, CV_DIST_L2, CV_DIST_C
或
CV_DIST_USER
.
CV_DIST_L1
或
CV_DIST_C
的情况,参数值被强制设定为 3, 因为 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=1.3693 CV_DIST_L2 (5×5): a=1, b=1.4, c=2.1969
下面用户自定义距离的的距离域示例 (黑点 (0) 在白色方块中间):
4.5 | 4 | 3.5 | 3 | 3.5 | 4 | 4.5 |
4 | 3 | 2.5 | 2 | 2.5 | 3 | 4 |
3.5 | 2.5 | 1.5 | 1 | 1.5 | 2.5 | 3.5 |
3 | 2 | 1 | 0 | 1 | 2 | 3 |
3.5 | 2.5 | 1.5 | 1 | 1.5 | 2.5 | 3.5 |
4 | 3 | 2.5 | 2 | 2.5 | 3 | 4 |
4.5 | 4 | 3.5 | 3 | 3.5 | 4 | 4.5 |
4.5 | 3.5 | 3 | 3 | 3 | 3.5 | 4.5 |
3.5 | 3 | 2 | 2 | 2 | 3 | 3.5 |
3 | 2 | 1.5 | 1 | 1.5 | 2 | 3 |
3 | 2 | 1 | 0 | 1 | 2 | 3 |
3 | 2 | 1.5 | 1 | 1.5 | 2 | 3 |
3.5 | 3 | 2 | 2 | 2 | 3 | 3.5 |
4 | 3.5 | 3 | 3 | 3 | 3.5 | 4 |
典型的使用快速粗略距离估计 CV_DIST_L2, 3×3 mask , 如果要更精确的距离估计,使用 CV_DIST_L2, 5×5 mask。
多维直方图
typedef struct CvHistogram { int header_size; /* 头尺寸 */ CvHistType type; /* 直方图类型 */ int flags; /* 直方图标识 */ int c_dims; /* 直方图维数 */ int dims[CV_HIST_MAX_DIM]; /* 每一维的尺寸 */ int mdims[CV_HIST_MAX_DIM]; /* 快速访问元素的系数 */ /* &m[a,b,c] = m + a*mdims[0] + b*mdims[1] + c*mdims[2] */ float* thresh[CV_HIST_MAX_DIM]; /* 每一维的直方块边界数组 */ float* array; /* 所有的直方图数据,扩展为单行 */ struct CvNode* root; /* 存储直方块的平衡树的根结点 */ CvSet* set; /* 内存存储仓的指针 (对平衡树而言) */ int* chdims[CV_HIST_MAX_DIM]; /* 快速计算的缓存 */ } CvHistogram;
创建直方图
CvHistogram* cvCreateHist( int dims, int* sizes, int type, float** ranges=NULL, int uniform=1 );
CV_HIST_ARRAY
意味着直方图数据表示为多维密集数组 CvMatND;
CV_HIST_TREE
意味着直方图数据表示为多维稀疏数组 CvSparseMat.
uniform
的值。这个范围的用处是确定何时计算直方图或决定反向映射(backprojected ),每个方块对应于输入图像的哪个/哪组值。
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 置零。
函数 cvCreateHist 创建一个指定尺寸的直方图,并且返回创建的直方图的指针。 如果数组的 ranges
是 0, 则直方块的范围必须由函数cvSetHistBinRanges 稍后指定。虽然 cvCalcHist 和 cvCalcBackProject 可以处理 8-比特图像而无需设置任何直方块的范围,但它们都被假设等分 0..255 之间的空间。
设置直方块的区间
void cvSetHistBinRanges( CvHistogram* hist, float** ranges, int uniform=1 );
函数 cvSetHistBinRanges 是一个独立的函数,完成直方块的区间设置。更多详细的关于参数 ranges
和 uniform
的描述,请参考函数 cvCalcHist , 该函数也可以初始化区间。直方块的区间的设置必须在计算直方图之前,或 在计算直方图的反射图之前。
释放直方图结构
void cvReleaseHist( CvHistogram** hist );
函数 cvReleaseHist 释放直方图 (头和数据). 指向直方图的指针被函数所清空。如果 *hist
指针已经为 NULL
, 则函数不做任何事情。
清除直方图
void cvClearHist( CvHistogram* hist );
函数 cvClearHist 当直方图是稠密数组时将所有直方块设置为 0,当直方图是稀疏数组时,除去所有的直方块。
从数组中创建直方图
CvHistogram* cvMakeHistHeaderForArray( int dims, int* sizes, CvHistogram* hist, float* data, float** ranges=NULL, int uniform=1 );
函数 cvMakeHistHeaderForArray 初始化直方图,其中头和直方块为用户所分配。以后不需要调用 cvReleaseHist 只有稠密直方图可以采用这种方法,函数返回 hist
.
查询直方块的值
#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) )
宏 cvQueryHistValue_*D 返回 1D, 2D, 3D 或 N-D 直方图的指定直方块的值。对稀疏直方图,如果方块在直方图中不存在,函数返回 0, 而且不创建新的直方块。
返回直方块的指针
#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 ))
宏 cvGetHistValue_*D 返回 1D, 2D, 3D 或 N-D 直方图的指定直方块的指针。对稀疏直方图,函数创建一个新的直方块,且设置其为 0,除非它已经存在。
发现最大和最小直方块
void cvGetMinMaxHistValue( const CvHistogram* hist, float* min_value, float* max_value, int* min_idx=NULL, int* max_idx=NULL );
函数 cvGetMinMaxHistValue 发现最大和最小直方块以及它们的位置。任何输出变量都是可选的。在具有同样值几个极值中,返回具有最小下标索引(以字母排列顺序定)的那一个。
归一化直方图
void cvNormalizeHist( CvHistogram* hist, double factor );
函数 cvNormalizeHist 通过缩放来归一化直方块,使得所有块的和等于 factor
.
对直方图取阈值
void cvThreshHist( CvHistogram* hist, double threshold );
函数 cvThreshHist 清除那些小于指定阈值得直方块
比较两个稠密直方图
double cvCompareHist( const CvHistogram* hist1, const CvHistogram* hist2, int method );
函数 cvCompareHist 采用下面指定的方法比较两个稠密直方图(H1
表示第一个, H2
- 第二个):
相关 (method=CV_COMP_CORREL): d(H1,H2)=sumI(H'1(I)•H'2(I))/sqrt(sumI[H'1(I)2]•sumI[H'2(I)2]) 其中 H'k(I)=Hk(I)-1/N•sumJHk(J) (N=number of histogram bins) Chi-square(method=CV_COMP_CHISQR): d(H1,H2)=sumI[(H1(I)-H2(I))/(H1(I)+H2(I))] 交叉 (method=CV_COMP_INTERSECT): d(H1,H2)=sumImax(H1(I),H2(I))
函数返回 d(H1,H2)
的值。
为了比较稀疏直方图或更一般的加权稀疏点集(译者注:直方图匹配是图像检索中的常用方法),考虑使用函数 cvCalcEMD 。
拷贝直方图
void cvCopyHist( const CvHistogram* src, CvHistogram** dst );
函数 cvCopyHist 对直方图作拷贝。如果第二个直方图指针 *dst
是 NULL, 则创建一个与 src
同样大小的直方图。否则,两个直方图必须大小和类型一致。然后函数将输入的直方块的值复制到输出的直方图中,并且设置取值范围与 src
的一致。
计算图像image(s) 的直方图
void cvCalcHist( IplImage** image, CvHistogram* hist, int accumulate=0, const CvArr* mask=NULL );
函数 cvCalcHist 计算单通道或多通道图像的直方图。 用来增加直方块的数组元素可从相应输入图像的同样位置提取。
#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}; float h_ranges[] = { 0, 180 }; /* hue varies from 0 (~0°red) to 180 (~360°red again) */ float s_ranges[] = { 0, 255 }; /* saturation varies from 0 (black-gray-white) to 255 (pure spectrum color) */ 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); } }
计算反向投影
void cvCalcBackProject( IplImage** image, CvArr* back_project, const CvHistogram* hist );
函数 cvCalcBackProject 直方图的反向投影. 对于所有输入的单通道图像同一位置的象素数组,该函数根据相应的象素数组(RGB),放置其对应的直方块的值到输出图像中。用统计学术语,输出图像象素点的值是观测数组在某个分布(直方图)下的的概率。 例如,为了发现图像中的红色目标,可以这么做:
用直方图比较来定位图像中的模板
void cvCalcBackProjectPatch( IplImage** image, CvArr* dst, CvSize patch_size, CvHistogram* hist, int method, float factor );
函数 cvCalcBackProjectPatch 通过输入图像补丁的直方图和给定直方图的比较,来计算反向投影。提取图像在 ROI 中每一个位置的某种测量结果产生了数组 image
. 这些结果可以是色调, x
差分, y
差分, Laplacian 滤波器, 有方向 Gabor 滤波器等中 的一个或多个。每种测量输出都被划归为它自己的单独图像。 image
图像数组是这些测量图像的集合。一个多维直方图 hist
从这些图像数组中被采样创建。最后直方图被归一化。直方图 hist
的维数通常很大等于图像数组 image
的元素个数。
在选择的 ROI 中,每一个新的图像被测量并且转换为一个图像数组。在以锚点为“补丁”中心的图像 image
区域中计算直方图 (如下图所示)。用参数 norm_factor
来归一化直方图,使得它可以与 hist
互相比较。计算出的直方图与直方图模型互相比较, (hist
使用函数 cvCompareHist ,比较方法是 method=method
). 输出结果被放置到概率图像 dst
补丁锚点的对应位置上。这个过程随着补丁滑过整个 ROI 而重复进行。迭代直方图的更新可以通过在原直方图中减除“补丁”已覆盖的尾象素点或者加上新覆盖的象素点来实现,这种更新方式可以节省大量的操作,尽管目前在函数体中还没有实现。
两个直方图相除
void cvCalcProbDensity( const CvHistogram* hist1, const CvHistogram* hist2, CvHistogram* dst_hist, double scale=255 );
函数 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)
所以输出的直方块小于尺度因子。
比较模板和重叠的图像区域
void cvMatchTemplate( const CvArr* image, const CvArr* templ, CvArr* result, int method );
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)=sumx',y'[T(x',y')-I(x+x',y+y')]2 method=CV_TM_SQDIFF_NORMED: R(x,y)=sumx',y'[T(x',y')-I(x+x',y+y')]2/sqrt[sumx',y'T(x',y')2•sumx',y'I(x+x',y+y')2] method=CV_TM_CCORR: R(x,y)=sumx',y'[T(x',y')•I(x+x',y+y')] method=CV_TM_CCORR_NORMED: R(x,y)=sumx',y'[T(x',y')•I(x+x',y+y')]/sqrt[sumx',y'T(x',y')2•sumx',y'I(x+x',y+y')2] method=CV_TM_CCOEFF: R(x,y)=sumx',y'[T'(x',y')•I'(x+x',y+y')], where T'(x',y')=T(x',y') - 1/(w•h)•sumx",y"T(x",y") (mean template brightness=>0) I'(x+x',y+y')=I(x+x',y+y') - 1/(w•h)•sumx",y"I(x+x",y+y") (mean patch brightness=>0) method=CV_TM_CCOEFF_NORMED: R(x,y)=sumx',y'[T'(x',y')•I'(x+x',y+y')]/sqrt[sumx',y'T'(x',y')2•sumx',y'I'(x+x',y+y')2]函数完成比较后,通过使用 cvMinMaxLoc找全局最小值CV_TM_SQDIFF*) 或者最大值 (CV_TM_CCORR* and CV_TM_CCOEFF*)。
比较两个形状
double cvMatchShapes( const void* object1, const void* object2, int method, double parameter=0 );
函数 cvMatchShapes 比较两个形状。 三个实现方法全部使用 Hu 矩 (见 cvGetHuMoments) (A
~ object1
, B
- object2
):
method=CV_CONTOUR_MATCH_I1: I1(A,B)=sumi=1..7abs(1/mAi - 1/mBi) method=CV_CONTOUR_MATCH_I2: I2(A,B)=sumi=1..7abs(mAi - mBi) method=CV_CONTOUR_MATCH_I3: I3(A,B)=sumi=1..7abs(mAi - mBi)/abs(mAi) 其中 mAi=sign(hAi)•log(hAi), mBi=sign(hBi)•log(hBi), hAi, hBi - A 和 B的Hu矩.
两个加权点集之间计算最小工作距离
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);
size1
×
(dims+1)
的浮点数矩阵,每一行依次存储点的权重和点的坐标。矩阵允许只有一列(即仅有权重),如果使用用户自定义的代价矩阵。
signature1
的格式一样
size2
×
(dims+1)
,尽管行数可以不同(列数要相同)。当一个额外的虚拟点加入
signature1
或
signature2
中的时候,权重也可不同。
CV_DIST_L1, CV_DIST_L2
, 和
CV_DIST_C
分别为标准的准则。
CV_DIST_USER
意味着使用用户自定义函数
distance_func
或预先计算好的代价矩阵
cost_matrix
。
size1
×
size2
的代价矩阵。
cost_matrix
和
distance_func
两者至少有一个必须为 NULL. 而且,如果使用代价函数,下边界无法计算,因为它需要准则函数。
size1
×
size2
流矩阵(flow matrix):
flowij
是从
signature1
的第 i 个点到
signature2
的第 j 个点的流(flow)。
*lower_bound
. 如果质心之间的距离大于获等于
*lower_bound
(这意味着签名之间足够远), 函数则不计算 EMD. 任何情况下,函数返回时
*lower_bound
都被设置为计算出来的质心距离。因此如果用户想同时计算质心距离和T EMD,
*lower_bound
应该被设置为 0.
函数 cvCalcEMD2 计算两个加权点集之间的移动距离或距离下界。在 [RubnerSept98] 中所描述的其中一个应用就是图像提取得多维直方图比较。 EMD 是一个使用某种单纯形算法(simplex algorithm)来解决的交通问题。其计算复杂度在最坏情况下是指数形式的,但是平均而言它的速度相当快。对实的准则,下边界的计算可以更快(使用线性时间算法),且它可用来粗略确定两个点集是否足够远以至无法联系到同一个目标上。
用多边形曲线逼近 Freeman 链
CvSeq* cvApproxChains( CvSeq* src_seq, CvMemStorage* storage, int method=CV_CHAIN_APPROX_SIMPLE, double parameter=0, int minimal_perimeter=0, int recursive=0 );
minimal_perimeter
轮廓。其它的链从结果中除去。
src_seq
中利用
h_next
和
v_next links
连接逼近所有可访问的链。如果为 0, 则仅逼近单链。
这是一个单独的逼近程序。 对同样的逼近标识,函数 cvApproxChains 与 cvFindContours 的工作方式一模一样。它返回发现的第一个轮廓的指针。其它的逼近模块,可以用返回结构中的 v_next
和 v_next
域来访问
初始化链读取
void cvStartReadChainPoints( CvChain* chain, CvChainPtReader* reader );
链的指针
reader
链的读取状态
函数 cvStartReadChainPoints 初始化一个特殊的读取器 (参考 Dynamic Data Structures 以获得关于集合与序列的更多内容).
得到下一个链的点
CvPoint cvReadChainPoint( CvChainPtReader* reader );
函数 cvReadChainPoint 返回当前链的点,并且更新读取位置。
用指定精度逼近多边形曲线
CvSeq* cvApproxPoly( const void* src_seq, int header_size, CvMemStorage* storage, int method, double parameter, int parameter2=0 );
CV_POLY_APPROX_DP
, 对应 Douglas-Peucker 算法.
CV_POLY_APPROX_DP
它是指定的逼近精度
src_seq
是序列,它表示要么逼近单个序列,要么在
src_seq
的同一个或低级层次上逼近所有序列 (参考 cvFindContours 中对轮廓继承结构的描述). 如果
src_seq
是点集的数组 ( CvMat*) , 参数指定曲线是闭合 (
parameter2
!=0) 还是非闭合 (
parameter2
=0).
函数 cvApproxPoly 逼近一个或多个曲线,并返回逼近结果。对多个曲线的逼近,生成的树将与输入的具有同样的结构。(1:1 的对应关系).
计算点集的最外面(up-right)矩形边界
CvRect cvBoundingRect( CvArr* points, int update=0 );
CvMat
)
rect
域得到。rect
域中 header.函数 cvBoundingRect 返回二维点集的最外面 (up-right)矩形边界。
计算整个轮廓或部分轮廓的面积
double cvContourArea( const CvArr* contour, CvSlice slice=CV_WHOLE_SEQ );
函数 cvContourArea 计算整个轮廓或部分轮廓的面积。 对后面的情况,面积表示轮廓部分和起始点连线构成的封闭部分的面积。如下图所示:
NOTE: 轮廓的方向影响面积的符号。因此函数也许会返回负的结果。应用函数 fabs()
得到面积的绝对值。
计算轮廓周长或曲线长度
double cvArcLength( const void* curve, CvSlice slice=CV_WHOLE_SEQ, int is_closed=-1 );
函数 cvArcLength 通过依次计算序列点之间的线段长度,并求和来得到曲线的长度。
创建轮廓的继承表示形式
CvContourTree* cvCreateContourTree( const CvSeq* contour, CvMemStorage* storage, double threshold );
函数 cvCreateContourTree 为输入轮廓 contour
创建一个二叉树,并返回树根的指针。如果参数 threshold
小于或等于 0 ,则函数创建一个完整的二叉树。如果 threshold
大于 0 , 函数用 threshold
指定的精度创建二叉树:如果基线的截断区域顶点小于threshold,该数就停止生长并作为函数的最终结果返回。
由树恢复轮廓
CvSeq* cvContourFromContourTree( const CvContourTree* tree, CvMemStorage* storage, CvTermCriteria criteria );
函数 cvContourFromContourTree 从二叉树恢复轮廓。参数 criteria
决定了重构的精度和使用树的数目及层次。所以它可建立逼近的轮廓。 函数返回重构的轮廓。
用树的形式比较两个轮廓
double cvMatchContourTrees( const CvContourTree* tree1, const CvContourTree* tree2, int method, double threshold );
CV_CONTOUR_TREES_MATCH_I1
。
函数 cvMatchContourTrees 计算两个轮廓树的匹配值。从树根开始通过逐层比较来计算相似度。如果某层的相似度小于 threshold
, 则中断比较过程,且返回当前的差值。
对两个给定矩形,寻找矩形边界
CvRect cvMaxRect( const CvRect* rect1, const CvRect* rect2 );
函数 cvMaxRect 寻找包含两个输入矩形的具有最小面积的矩形边界。
旋转的二维盒子
typedef struct CvBox2D { CvPoint2D32f center; /* 盒子的中心 */ CvSize2D32f size; /* 盒子的长和宽 */ float angle; /* 水平轴与第一个边的夹角,用弧度表示*/ } CvBox2D;
寻找盒子的顶点
void cvBoxPoints( CvBox2D box, CvPoint2D32f pt[4] );
函数 cvBoxPoints 计算输入的二维盒子的定点。下面是函数代码:
void cvBoxPoints( CvBox2D box, CvPoint2D32f pt[4] ) { float a = (float)cos(box.angle)*0.5f; float b = (float)sin(box.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; }
二维点集的椭圆拟合
CvBox2D cvFitEllipse2( const CvArr* points );
函数 cvFitEllipse 对给定的一组二维点集作椭圆的最佳拟合(最小二乘意义上的)。返回的结构与 cvEllipse 中的意义类似,除了 size
表示椭圆轴的整个长度,而不是一半长度。
2D 或 3D 点集的直线拟合
void cvFitLine( const CvArr* points, int dist_type, double param, double reps, double aeps, float* line );
(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)=r2/2 (最简单和最快的最小二乘法) dist_type=CV_DIST_L1 (L1): ρ(r)=r dist_type=CV_DIST_L12 (L1-L2): ρ(r)=2•[sqrt(1+r2/2) - 1] dist_type=CV_DIST_FAIR (Fair): ρ(r)=C2•[r/C - log(1 + r/C)], C=1.3998 dist_type=CV_DIST_WELSCH (Welsch): ρ(r)=C2/2•[1 - exp(-(r/C)2)], C=2.9846 dist_type=CV_DIST_HUBER (Huber): ρ(r)= r2/2, if r < C C•(r-C/2), otherwise; C=1.345
发现点集的凸外形
CvSeq* cvConvexHull2( const CvArr* input, void* hull_storage=NULL, int orientation=CV_CLOCKWISE, int return_points=0 );
CV_CLOCKWISE
or
CV_COUNTER_CLOCKWISE
)
hull_storage
为数组情况下的顶点形式 (indices) 以及
hull_storag
为内存存储模式下的点集形式(points)。
函数 cvConvexHull2 使用 Sklansky 算法计算 2D 点集的凸外形。如果 hull_storage
是内存存储仓, 函数根据 return_points
的值,创建一个包含外形的点集或指向这些点的指针的序列。
#include "cv.h" #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()%100 + 1, hullcount; CvPoint pt0; #if !ARRAY CvSeq* ptseq = cvCreateSeq( CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvContour), sizeof(CvPoint), storage ); CvSeq* 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; cvSeqPush( ptseq, &pt0 ); } hull = cvConvexHull2( ptseq, 0, CV_CLOCKWISE, 0 ); hullcount = hull->total; #else 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; 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 ); free( hull ); #endif } }
测试轮廓的凸性
int cvCheckContourConvexity( const CvArr* contour );
函数 cvCheckContourConvexity 输入的轮廓是否为凸的。必须是简单轮廓,比如没有自交叉。
用来描述一个简单轮廓凸性缺陷的结构体
typedef struct CvConvexityDefect { CvPoint* start; /* 缺陷开始的轮廓点 */ CvPoint* end; /* 缺陷结束的轮廓点 */ CvPoint* depth_point; /* 缺陷中距离凸形最远的轮廓点(谷底) */ float depth; /* 谷底距离凸形的深度*/ } CvConvexityDefect;
发现轮廓凸形缺陷
CvSeq* cvConvexityDefects( const CvArr* contour, const CvArr* convexhull, CvMemStorage* storage=NULL );
return_points
应该设置为 0.
函数 cvConvexityDefects 发现输入轮廓的所有凸性缺陷,并且返回 CvConvexityDefect 结构序列。
对给定的 2D 点集,寻找最小面积的包围矩形
CvBox2D cvMinAreaRect2( const CvArr* points, CvMemStorage* storage=NULL );
函数 cvMinAreaRect2 通过建立凸外形并且旋转外形以寻找给定 2D 点集的最小面积的包围矩形.
对给定的 2D 点集,寻找最小面积的包围圆形
int cvMinEnclosingCircle( const CvArr* points, CvPoint2D32f* center, float* radius );
函数 cvMinEnclosingCircle 对给定的 2D 点集迭代寻找最小面积的包围圆形。如果产生的圆包含所有点,返回非零。否则返回零(算法失败)。
计算轮廓的 pair-wise 几何直方图
void cvCalcPGH( const CvSeq* contour, CvHistogram* hist );
函数 cvCalcPGH 计算轮廓的 2D pair-wise(Hunnish: 不知如何翻译,只好保留) 几何直方图 (pair-wise geometrical histogram :PGH), 算法描述见 [Iivarinen97]. 算法考虑的每一对轮廓边缘。计算每一对边缘之间的夹角以及最大最小距离。具体做法是,轮流考虑每一个边缘做为基准,函数循环遍历所有边缘。在考虑基准边缘和其它边缘的时候, 选择非基准线上的点到基准线上的最大和最小距离。边缘之间的角度定义了直方图的行,而在其中增加对应计算出来的最大和最小距离的所有直方块, (即直方图是 [Iivarninen97] 定义中的转置). 该直方图用来做轮廓匹配。
平面划分
#define CV_SUBDIV2D_FIELDS() \ CV_GRAPH_FIELDS() \ int quad_edges; \ int is_geometry_valid; \ CvSubdiv2DEdge recent_edge; \ CvPoint2D32f topleft; \ CvPoint2D32f bottomright; typedef struct CvSubdiv2D { CV_SUBDIV2D_FIELDS() } CvSubdiv2D;
平面划分是将一个平面分割为一组互不重叠的能够覆盖整个平面的区域P(facets)。上面结构描述了建立在 2D 点集上的划分结构,其中点集互相连接并且构成平面图形,该图形通过结合一些无限连接外部划分点(称为凸形点)的边缘,将一个平面用边按照其边缘划分成很多小区域(facets)。
对于每一个划分操作,都有一个对偶划分与之对应,对偶的意思是小区域和点(划分的顶点)变换角色,即在对偶划分中,小区域被当做一个顶点(以下称之为虚拟点),而原始的划分顶点被当做小区域。在如下所示的图例中,原始的划分用实线来表示,而对偶划分用点线来表示。
OpenCV 使用Delaunay's 算法将平面分割成小的三角形区域。分割的实现通过从一个假定的三角形(该三角形确保包括所有的分割点)开始不断迭代来完成。在这种情况下,对偶划分就是输入的2d点集的 Voronoi图表。这种划分可以用于对一个平面的3d分段变换、形态变换、平面点的快速定位以及建立特定的图结构 (比如 NNG,RNG等等)。
平面划分中的Quad-edge(四方边缘结构)
/* quad-edge中的一条边缘,低两位表示该边缘的索引号,其它高位表示边缘指针。 */ typedef long CvSubdiv2DEdge; /* 四方边缘的结构场 */ #define CV_QUADEDGE2D_FIELDS() \ int flags; \ struct CvSubdiv2DPoint* pt[4]; \ CvSubdiv2DEdge next[4]; typedef struct CvQuadEdge2D { CV_QUADEDGE2D_FIELDS() } CvQuadEdge2D;
Quad-edge(译者注:以下称之为四方边缘结构)是平面划分的基元,其中包括四个边缘 (e, eRot 以及它们的逆)。
原始和对偶划分点
#define CV_SUBDIV2D_POINT_FIELDS()\ int flags; \ CvSubdiv2DEdge first; \ CvPoint2D32f pt; #define CV_SUBDIV2D_VIRTUAL_POINT_FLAG (1 << 30) typedef struct CvSubdiv2DPoint { CV_SUBDIV2D_POINT_FIELDS() } CvSubdiv2DPoint;
返回给定的边缘之一
CvSubdiv2DEdge cvSubdiv2DGetEdge( CvSubdiv2DEdge edge, CvNextEdgeType type ); #define cvSubdiv2DNextEdge( edge ) cvSubdiv2DGetEdge( edge, CV_NEXT_AROUND_ORG )
eOnext
on the picture above if e
is the input edge)eDnext
)eRnext
)eLnext
)eLnext
)eRnext
)eOnext
)eDnext
)函数 cvSubdiv2DGetEdge 返回与输入边缘相关的边缘
返回同一个四方边缘结构中的另一条边缘
CvSubdiv2DEdge cvSubdiv2DRotateEdge( CvSubdiv2DEdge edge, int rotate );
e
,如果e是输入边缘
)eRot
)函数 cvSubdiv2DRotateEdge 根据输入的边缘返回四方边缘结构中的一条边缘
返回边缘的原点
CvSubdiv2DPoint* cvSubdiv2DEdgeOrg( CvSubdiv2DEdge edge );
函数 cvSubdiv2DEdgeOrg 返回边缘的原点。如果该边缘是从对偶划分得到并且虚点坐标还没有计算出来,可能返回空指针。虚点可以用函数来cvCalcSubdivVoronoi2D计算。
Returns edge destination
CvSubdiv2DPoint* cvSubdiv2DEdgeDst( CvSubdiv2DEdge edge );
函数 cvSubdiv2DEdgeDst 返回边缘的终点。如果该边缘是从对偶划分得到并且虚点坐标还没有计算出来,可能返回空指针。虚点可以用函数来cvCalcSubdivVoronoi2D计算。
生成的空Delaunay 三角测量
CvSubdiv2D* cvCreateSubdivDelaunay2D( CvRect rect, CvMemStorage* storage );
函数 cvCreateSubdivDelaunay2D 生成一个空的Delaunay 划分, 其中2d points可以进一步使用函数 cvSubdivDelaunay2DInsert来添加。所有的点一定要在指定的四方形中添加,否则就会报运行错误。
向 Delaunay三角测量中插入一个点
CvSubdiv2DPoint* cvSubdivDelaunay2DInsert( CvSubdiv2D* subdiv, CvPoint2D32f pt);
函数 cvSubdivDelaunay2DInsert 向划分的结构中插入一个点并且正确地改变划分的拓朴结构。如果划分结构中已经存在一个相同的坐标点,则不会有新点插入。该函数返回指向已插入点的指针。在这个截断,不计算任何虚点坐标。
在 Delaunay三角测量中定位输入点
CvSubdiv2DPointLocation cvSubdiv2DLocate( CvSubdiv2D* subdiv, CvPoint2D32f pt, CvSubdiv2DEdge* edge, CvSubdiv2DPoint** vertex=NULL );
函数 cvSubdiv2DLocate 在划分中定位输入点,共有5种类型:
*edge
中包含小区域的边缘之一。*edge
包含此边缘。*vertex
中包括指向该顶点的指针;根据输入点,找到其最近的划分顶点
CvSubdiv2DPoint* cvFindNearestPoint2D( CvSubdiv2D* subdiv, CvPoint2D32f pt );
函数 cvFindNearestPoint2D 是另一个定位输入点的函数。该函数找到输入点的最近划分顶点。尽管划分出的小区域(facet)被用来作为起始点,但是输入点不一定非得在最终找到的顶点所在的小区域之内。该函数返回指向找到的划分顶点的指针。
计算Voronoi图表的细胞结构
void cvCalcSubdivVoronoi2D( CvSubdiv2D* subdiv );
函数 cvCalcSubdivVoronoi2D 计算虚点的坐标,所有与原划分中的某顶点相对应的虚点形成了(当他们相互连接时)该顶点的Voronoi 细胞的边界。
移除所有的虚点
void cvClearSubdivVoronoi2D( CvSubdiv2D* subdiv );
函数 cvClearSubdivVoronoi2D 移除所有的虚点。当划分的结果被函数cvCalcSubdivVoronoi2D的前一次调用更改时,该函数被cvCalcSubdivVoronoi2D内部调用 。
还有一些其它的底层处理函数与平面划分操作协同工作,参见 cv.h 及源码。生成 delaunay.c 三角测量以及2d随机点集的Voronoi 图表的演示代码可以在 opencv/samples/c目录下的delaunay.c 文件中找到。
将帧叠加到累积器(accumulator)中
void cvAcc( const CvArr* image, CvArr* sum, const CvArr* mask=NULL );
函数 cvAcc 将整个图像 image
或某个选择区域叠加到 sum
中:
sum(x,y)=sum(x,y)+image(x,y) if mask(x,y)!=0
叠加输入图像的平方到累积器中
void cvSquareAcc( const CvArr* image, CvArr* sqsum, const CvArr* mask=NULL );
函数 cvSquareAcc 叠加输入图像 image
或某个选择区域的二次方,到累积器 sqsum
中
sqsum(x,y)=sqsum(x,y)+image(x,y)2 if mask(x,y)!=0
将两幅输入图像的乘积叠加到累积器中
void cvMultiplyAcc( const CvArr* image1, const CvArr* image2, CvArr* acc, const CvArr* mask=NULL );
函数 cvMultiplyAcc 叠加两个输入图像的乘积到累积器 acc
:
acc(x,y)=acc(x,y) + image1(x,y)•image2(x,y) if mask(x,y)!=0
更新 running average ( Hunnish: 不知道 running average 如何翻译才恰当)
void cvRunningAvg( const CvArr* image, CvArr* acc, double alpha, const CvArr* mask=NULL );
函数 cvRunningAvg 计算输入图像 image
的加权和,以及累积器 acc
使得 acc
成为帧序列的一个 running average:
acc(x,y)=(1-α)•acc(x,y) + α•image(x,y) if mask(x,y)!=0
其中 α (alpha) 调节更新速率 (累积器以多快的速率忘掉前面的帧).
去掉影像(silhouette) 以更新运动历史图像
void cvUpdateMotionHistory( const CvArr* silhouette, CvArr* mhi, double timestamp, double duration );
timestamp
一样的时间单位
函数 cvUpdateMotionHistory 用下面方式更新运动历史图像:
mhi(x,y)=timestamp if silhouette(x,y)!=0 0 if silhouette(x,y)=0 and mhi(x,y)<timestamp-duration mhi(x,y) otherwise
也就是,MHI(motion history image) 中在运动发生的象素点被设置为当前时间戳,而运动发生较久的象素点被清除。
计算运动历史图像的梯度方向
void cvCalcMotionGradient( const CvArr* mhi, CvArr* mask, CvArr* orientation, double delta1, double delta2, int aperture_size=3 );
min(delta1,delta2) <= M(x,y)-m(x,y) <= max(delta1,delta2).
函数 cvCalcMotionGradient 计算 MHI 的差分 Dx
和 Dy
,然后计算梯度方向如下式:
orientation(x,y)=arctan(Dy(x,y)/Dx(x,y))
其中都要考虑 Dx(x,y)
' 和 Dy(x,y)
' 的符号 (如 cvCartToPolar 类似). 然后填充 mask
以表示哪些方向是正确的(见 delta1
和delta2
的描述).
计算某些选择区域的全局运动方向
double cvCalcGlobalOrientation( const CvArr* orientation, const CvArr* mask, const CvArr* mhi, double timestamp, double duration );
函数 cvCalcGlobalOrientation 在选择的区域内计算整个运动方向,并且返回 0° 到 360° 之间的角度值。首先函数创建运动直方图,寻找基本方向做为直方图最大值的坐标。然后函数计算与基本方向的相对偏移量,做为所有方向向量的加权和:运行越近,权重越大。得到的角度是基本方向和偏移量的循环和。
将整个运动分割为独立的运动部分
CvSeq* cvSegmentMotion( const CvArr* mhi, CvArr* seg_mask, CvMemStorage* storage, double timestamp, double seg_thresh );
函数 cvSegmentMotion 寻找所有的运动分割,并且在seg_mask
用不同的单独数字(1,2,...)标识它们。它也返回一个具有CvConnectedComp 结构的序列,其中每个结构对应一个运动部件。在这之后,每个运动部件的运动方向就可以被函数cvCalcGlobalOrientation 利用提取的特定部件的掩模(mask)计算出来(使用 cvCmp)
在反向投影图中发现目标中心
int cvMeanShift( const CvArr* prob_image, CvRect window, CvTermCriteria criteria, CvConnectedComp* comp );
comp->rect
字段) 与窗口内部所有象素点的和 (
comp->area
字段).
函数 cvMeanShift 在给定反向投影和初始搜索窗口位置的情况下,用迭代方法寻找目标中心。当搜索窗口中心的移动小于某个给定值时或者函数已经达到最大迭代次数时停止迭代。 函数返回迭代次数。
发现目标中心,尺寸和方向
int cvCamShift( const CvArr* prob_image, CvRect window, CvTermCriteria criteria, CvConnectedComp* comp, CvBox2D* box=NULL );
comp->rect
字段) 与窗口内部所有象素点的和 (
comp->area
字段).
NULL
, 则包含目标的尺寸和方向。
函数 cvCamShift 实现了 CAMSHIFT 目标跟踪算法([Bradski98]). 首先它调用函数 cvMeanShift 寻找目标中心,然后计算目标尺寸和方向。最后返回函数 cvMeanShift 中的迭代次数。
CvCamShiftTracker 类在 cv.hpp 中被声明,函数实现了彩色目标的跟踪。
改变轮廓位置使得它的能量最小
void cvSnakeImage( const IplImage* image, CvPoint* points, int length, float* alpha, float* beta, float* gamma, int coeff_usage, CvSize win, CvTermCriteria criteria, int calc_gradient=1 );
length
的浮点数数组,每个轮廓点有一个权
alpha
类似
alpha
类似
CV_VALUE
表示每个 alpha, beta, gamma
都是指向为所有点所用的一个单独数值;CV_ARRAY
表示每个 alpha, beta, gamma
是一个指向系数数组的指针,snake 上面各点的系数都不相同。因此,各个系数数组必须与轮廓具有同样的大小。所有数组必须与轮廓具有同样大小win.width
和
win.height
都必须是奇数
函数 cvSnakeImage 更新 snake 是为了最小化 snake 的整个能量,其中能量是依赖于轮廓形状的内部能量(轮廓越光滑,内部能量越小)以及依赖于能量场的外部能量之和,外部能量通常在哪些局部能量极值点中达到最小值(这些局部能量极值点与图像梯度表示的图像边缘相对应)。
参数 criteria.epsilon
用来定义必须从迭代中除掉以保证迭代正常运行的点的最少数目。
如果在迭代中去掉的点数目小于 criteria.epsilon
或者函数达到了最大的迭代次数 criteria.max_iter
,则终止函数。
计算两幅图像的光流
void cvCalcOpticalFlowHS( const CvArr* prev, const CvArr* curr, int use_previous, CvArr* velx, CvArr* vely, double lambda, CvTermCriteria criteria );
函数 cvCalcOpticalFlowHS 为输入图像的每一个象素计算光流,使用 Horn & Schunck 算法 [Horn81].
计算两幅图像的光流
void cvCalcOpticalFlowLK( const CvArr* prev, const CvArr* curr, CvSize win_size, CvArr* velx, CvArr* vely );
函数 cvCalcOpticalFlowLK 为输入图像的每一个象素计算光流,使用 Lucas & Kanade 算法 [Lucas81].
用块匹配方法计算两幅图像的光流
void cvCalcOpticalFlowBM( const CvArr* prev, const CvArr* curr, CvSize block_size, CvSize shift_size, CvSize max_range, int use_previous, CvArr* velx, CvArr* vely );
velx
大小一样,32-比特,浮点数, 单通道.
函数 cvCalcOpticalFlowBM 为重叠块 block_size.width×block_size.height
中的每一个象素计算光流,因此其速度域小于整个图像的速度域。对每一个在图像 prev
中的块,函数试图在 curr
中某些原始块或其偏移 (velx(x0,y0),vely(x0,y0)) 块的邻域里寻找类似的块,如同在前一个函数调用中所计算的类似(如果 use_previous=1
)
计算一个稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法
void cvCalcOpticalFlowPyrLK( const CvArr* prev, const CvArr* curr, CvArr* prev_pyr, CvArr* curr_pyr, const CvPoint2D32f* prev_features, CvPoint2D32f* curr_features, int count, CvSize win_size, int level, char* status, float* track_error, CvTermCriteria criteria, int flags );
t
的第一帧
t + dt
的第二帧
NULL
, 则缓存必须有足够的空间来存储金字塔从层 1 到层 #
level
的内容。尺寸
(image_width+8)*image_height/3
比特足够了
prev_pyr
类似, 用于第二帧
0
, 不使用金字塔 (即金字塔为单层), 如果为
1
, 使用两层,下面依次类推。
NULL
.
CV_LKFLOW_PYR_A_READY
, 在调用之前,先计算第一帧的金字塔CV_LKFLOW_PYR_B_READY
, 在调用之前,先计算第二帧的金字塔CV_LKFLOW_INITIAL_GUESSES
, 在调用之前,数组 B 包含特征的初始坐标 (Hunnish: 在本节中没有出现数组 B,不知是指的哪一个)函数 cvCalcOpticalFlowPyrLK 实现了金字塔中 Lucas-Kanade 光流计算的稀疏迭代版本 ([Bouguet00])。 它根据给出的前一帧特征点坐标计算当前视频帧上的特征点坐标。 函数寻找具有子象素精度的坐标值。
两个参数 prev_pyr
和 curr_pyr
都遵循下列规则: 如果图像指针为 0, 函数在内部为其分配缓存空间,计算金字塔,然后再处理过后释放缓存。 否则,函数计算金字塔且存储它到缓存中,除非设置标识 CV_LKFLOW_PYR_A[B]_READY
。 图像应该足够大以便能够容纳 Gaussian 金字塔数据。 调用函数以后,金字塔被计算而且相应图像的标识也被设置,为下一次调用准备就绪 (比如:对除了第一个图像的所有图像序列,标识 CV_LKFLOW_PYR_A_READY
被设置).
Kalman 滤波器状态
typedef struct CvKalman { int MP; /* 测量向量维数 */ int DP; /* 状态向量维数 */ int CP; /* 控制向量维数 */ /* 向后兼容字段 */ #if 1 float* PosterState; /* =state_pre->data.fl */ float* PriorState; /* =state_post->data.fl */ float* DynamMatr; /* =transition_matrix->data.fl */ float* MeasurementMatr; /* =measurement_matrix->data.fl */ float* MNCovariance; /* =measurement_noise_cov->data.fl */ float* PNCovariance; /* =process_noise_cov->data.fl */ float* KalmGainMatr; /* =gain->data.fl */ float* PriorErrorCovariance;/* =error_cov_pre->data.fl */ float* PosterErrorCovariance;/* =error_cov_post->data.fl */ float* Temp1; /* temp1->data.fl */ float* Temp2; /* temp2->data.fl */ #endif CvMat* state_pre; /* 预测状态 (x'(k)): x(k)=A*x(k-1)+B*u(k) */ CvMat* state_post; /* 矫正状态 (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k)) */ CvMat* transition_matrix; /* 状态传递矩阵 state transition matrix (A) */ CvMat* control_matrix; /* 控制矩阵 control matrix (B) (如果没有控制,则不使用它)*/ CvMat* measurement_matrix; /* 测量矩阵 measurement matrix (H) */ CvMat* process_noise_cov; /* 处理噪声相关矩阵 process noise covariance matrix (Q) */ CvMat* measurement_noise_cov; /* 测量噪声相关矩阵 measurement noise covariance matrix (R) */ CvMat* error_cov_pre; /* 先验错误估计相关矩阵 priori error estimate covariance matrix (P'(k)): P'(k)=A*P(k-1)*At + Q)*/ CvMat* gain; /* Kalman 增益矩阵 gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)*/ CvMat* error_cov_post; /* 后验错误估计相关矩阵 posteriori error estimate covariance matrix (P(k)): P(k)=(I-K(k)*H)*P'(k) */ CvMat* temp1; /* 临时矩阵 temporary matrices */ CvMat* temp2; CvMat* temp3; CvMat* temp4; CvMat* temp5; } CvKalman;
(Hunnish: 害怕有翻译上的不准确,因此在翻译注释时,保留了原来的英文术语,以便大家对照)
结构 CvKalman 用来保存 Kalman 滤波器状态。它由函数 cvCreateKalman 创建,由函数f cvKalmanPredict 和 cvKalmanCorrect更新,由 cvReleaseKalman 释放. 通常该结构是为标准 Kalman 所使用的 (符号和公式都借自非常优秀的 Kalman 教程 [Welch95]):
xk=A•xk-1+B•uk+wk 译者注:系统运动方程 zk=H•xk+vk, 译者注:系统观测方程
其中:
xk (xk-1) - 系统在时刻 k (k-1) 的状态向量 (state of the system at the moment k (k-1)) zk - 在时刻 k 的系统状态测量向量 (measurement of the system state at the moment k) uk - 应用于时刻 k 的外部控制 (external control applied at the moment k) wk 和 vk 分别为正态分布的运动和测量噪声 p(w) ~ N(0,Q) p(v) ~ N(0,R), 即, Q - 运动噪声的相关矩阵,常量或变量 R - 测量噪声的相关矩阵,常量或变量
对标准 Kalman 滤波器,所有矩阵: A, B, H, Q 和 R 都是通过 cvCreateKalman 在分配结构 CvKalman 时初始化一次。但是,同样的结构和函数,通过在当前系统状态邻域中线性化扩展 Kalman 滤波器方程,可以用来模拟扩展 Kalman 滤波器,在这种情况下, A, B, H (也许还有 Q 和 R) 在每一步中都被更新。
分配 Kalman 滤波器结构
CvKalman* cvCreateKalman( int dynam_params, int measure_params, int control_params=0 );
函数 cvCreateKalman 分配 CvKalman 以及它的所有矩阵和初始参数
释放 Kalman 滤波器结构
void cvReleaseKalman( CvKalman** kalman );
函数 cvReleaseKalman 释放结构 CvKalman 和里面所有矩阵
估计后来的模型状态
const CvMat* cvKalmanPredict( CvKalman* kalman, const CvMat* control=NULL ); #define cvKalmanUpdateByTime cvKalmanPredict
control_params
=0) 应该为 NULL
函数 cvKalmanPredict 根据当前状态估计后来的随机模型状态,并存储于 kalman->state_pre
:
x'k=A•xk+B•uk
P'k=A•Pk-1*AT + Q,
其中
x'k 是预测状态 (kalman->state_pre),
xk-1 是前一步的矫正状态 (kalman->state_post)
(应该在开始的某个地方初始化,即缺省为零向量),
uk 是外部控制(control
参数),
P'k 是先验误差相关矩阵 (kalman->error_cov_pre)
Pk-1 是前一步的后验误差相关矩阵(kalman->error_cov_post)
(应该在开始的某个地方初始化,即缺省为单位矩阵),
函数返回估计得到的状态值
调节模型状态
const CvMat* cvKalmanCorrect( CvKalman* kalman, const CvMat* measurement ); #define cvKalmanUpdateByMeasurement cvKalmanCorrect
函数 cvKalmanCorrect 在给定的模型状态的测量基础上,调节随机模型状态:
Kk=P'k•HT•(H•P'k•HT+R)-1
xk=x'k+Kk•(zk-H•x'k)
Pk=(I-Kk•H)•P'k
其中
zk - 给定测量(mesurement
parameter)
Kk - Kalman "增益" 矩阵
函数存储调节状态到 kalman->state_post
中并且输出时返回它
#include "cv.h" #include "highgui.h" #include <math.h> int main(int argc, char** argv) { /* A matrix data */ const float A[] = { 1, 1, 0, 1 }; IplImage* img = cvCreateImage( cvSize(500,500), 8, 3 ); CvKalman* kalman = cvCreateKalman( 2, 1, 0 ); /* state is (phi, delta_phi) - angle and angle increment */ CvMat* state = cvCreateMat( 2, 1, CV_32FC1 ); CvMat* process_noise = cvCreateMat( 2, 1, CV_32FC1 ); /* only phi (angle) is measured */ CvMat* measurement = cvCreateMat( 1, 1, CV_32FC1 ); CvRandState rng; int code = -1; cvRandInit( &rng, 0, 1, -1, CV_RAND_UNI ); cvZero( measurement ); cvNamedWindow( "Kalman", 1 ); for(;;) { cvRandSetRange( &rng, 0, 0.1, 0 ); rng.disttype = CV_RAND_NORMAL; cvRand( &rng, state ); memcpy( kalman->transition_matrix->data.fl, A, sizeof(A)); cvSetIdentity( kalman->measurement_matrix, cvRealScalar(1) ); cvSetIdentity( kalman->process_noise_cov, cvRealScalar(1e-5) ); cvSetIdentity( kalman->measurement_noise_cov, cvRealScalar(1e-1) ); cvSetIdentity( kalman->error_cov_post, cvRealScalar(1)); /* choose random initial state */ cvRand( &rng, kalman->state_post ); rng.disttype = CV_RAND_NORMAL; for(;;) { #define calc_point(angle) \ cvPoint( cvRound(img->width/2 + img->width/3*cos(angle)), \ cvRound(img->height/2 - img->width/3*sin(angle))) float state_angle = state->data.fl[0]; CvPoint state_pt = calc_point(state_angle); /* predict point position */ const CvMat* prediction = cvKalmanPredict( kalman, 0 ); float predict_angle = prediction->data.fl[0]; CvPoint predict_pt = calc_point(predict_angle); float measurement_angle; CvPoint measurement_pt; cvRandSetRange( &rng, 0, sqrt(kalman->measurement_noise_cov->data.fl[0]), 0 ); cvRand( &rng, measurement ); /* generate measurement */ cvMatMulAdd( kalman->measurement_matrix, state, measurement, measurement ); measurement_angle = measurement->data.fl[0]; measurement_pt = calc_point(measurement_angle); /* plot points */ #define draw_cross( center, color, d ) \ cvLine( img, cvPoint( center.x - d, center.y - d ), \ cvPoint( center.x + d, center.y + d ), color, 1, 0 ); \ cvLine( img, cvPoint( center.x + d, center.y - d ), \ cvPoint( center.x - d, center.y + d ), color, 1, 0 ) cvZero( img ); draw_cross( state_pt, CV_RGB(255,255,255), 3 ); draw_cross( measurement_pt, CV_RGB(255,0,0), 3 ); draw_cross( predict_pt, CV_RGB(0,255,0), 3 ); cvLine( img, state_pt, predict_pt, CV_RGB(255,255,0), 3, 0 ); /* adjust Kalman filter state */ cvKalmanCorrect( kalman, measurement ); cvRandSetRange( &rng, 0, sqrt(kalman->process_noise_cov->data.fl[0]), 0 ); cvRand( &rng, process_noise ); cvMatMulAdd( kalman->transition_matrix, state, process_noise, state ); cvShowImage( "Kalman", img ); code = cvWaitKey( 100 ); if( code > 0 ) /* break current simulation by pressing a key */ break; } if( code == 27 ) /* exit by ESCAPE */ break; } return 0; }
ConDensaation 状态
typedef struct CvConDensation { int MP; // 测量向量的维数: Dimension of measurement vector int DP; // 状态向量的维数: Dimension of state vector float* DynamMatr; // 线性动态系统矩阵:Matrix of the linear Dynamics system float* State; // 状态向量: Vector of State int SamplesNum; // 粒子数: Number of the Samples float** flSamples; // 粒子向量数组: array of the Sample Vectors float** flNewSamples; // 粒子向量临时数组: temporary array of the Sample Vectors float* flConfidence; // 每个粒子的置信度(译者注:也就是粒子的权值):Confidence for each Sample float* flCumulative; // 权值的累计: Cumulative confidence float* Temp; // 临时向量:Temporary vector float* RandomSample; // 用来更新粒子集的随机向量: RandomVector to update sample set CvRandState* RandS; // 产生随机向量的结构数组: Array of structures to generate random vectors } CvConDensation;
结构 CvConDensation中条件概率密度传播(译者注:粒子滤波的一种特例)(Con-Dens-Aation: 单词 CONditional DENSity propagATION 的缩写)跟踪器的状态。该算法描述可参考http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/ISARD1/condensation.html
分配 ConDensation 滤波器结构
CvConDensation* cvCreateConDensation( int dynam_params, int measure_params, int sample_count );
函数 cvCreateConDensation 创建结构 CvConDensation 并且返回结构指针。
释放 ConDensation 滤波器结构
void cvReleaseConDensation( CvConDensation** condens );
函数 cvReleaseConDensation 释放结构 CvConDensation (见cvConDensation) 并且清空所有事先被开辟的内存空间。
初始化 ConDensation 算法中的粒子集
void cvConDensInitSampleSet( CvConDensation* condens, CvMat* lower_bound, CvMat* upper_bound );
函数 cvConDensInitSampleSet 在指定区间内填充结构 CvConDensation 中的样例数组。
估计下个模型状态
void cvConDensUpdateByTime( CvConDensation* condens );
函数 cvConDensUpdateByTime 从当前状态估计下一个随机模型状态。
目标检测方法最初由Paul Viola [Viola01]提出,并由Rainer Lienhart [Lienhart02]对这一方法进行了改善. 首先,利用样本(大约几百幅样本图片)的 harr 特征进行分类器训练,得到一个级联的boosted分类器。训练样本分为正例样本和反例样本,其中正例样本是指待检目标样本(例如人脸或汽车等),反例样本指其它任意图片,所有的样本图片都被归一化为同样的尺寸大小(例如,20x20)。
分类器训练完以后,就可以应用于输入图像中的感兴趣区域(与训练样本相同的尺寸)的检测。检测到目标区域(汽车或人脸)分类器输出为1,否则输出为0。为了检测整副图像,可以在图像中移动搜索窗口,检测每一个位置来确定可能的目标。 为了搜索不同大小的目标物体,分类器被设计为可以进行尺寸改变,这样比改变待检图像的尺寸大小更为有效。所以,为了在图像中检测未知大小的目标物体,扫描程序通常需要用不同比例大小的搜索窗口对图片进行几次扫描。
分类器中的“级联”是指最终的分类器是由几个简单分类器级联组成。在图像检测中,被检窗口依次通过每一级分类器, 这样在前面几层的检测中大部分的候选区域就被排除了,全部通过每一级分类器检测的区域即为目标区域。 目前支持这种分类器的boosting技术有四种: Discrete Adaboost, Real Adaboost, Gentle Adaboost and Logitboost。"boosted" 即指级联分类器的每一层都可以从中选取一个boosting算法(权重投票),并利用基础分类器的自我训练得到。基础分类器是至少有两个叶结点的决策树分类器。 海尔特征是基础分类器的输入,主要描述如下。目前的算法主要利用下面的海尔特征。
每个特定分类器所使用的特征用形状、感兴趣区域中的位置以及比例系数(这里的比例系数跟检测时候采用的比例系数是不一样的,尽管最后会取两个系数的乘积值)来定义。例如在第三行特征(2c)的情况下,响应计算为覆盖全部特征整个矩形框(包括两个白色矩形框和一个黑色矩形框)象素的和减去黑色矩形框内象素和的三倍 。每个矩形框内的象素和都可以通过积分图象很快的计算出来。(察看下面和对cvIntegral的描述).
通过HaarFaceDetect 的演示版可以察看目标检测的工作情况。
下面只是检测部分的参考手册。 haartraining
是它的一个单独的应用,可以用来对系列样本训练级联的 boosted分类器。详细察看opencv/apps/haartraining
。
Boosted Haar 分类器结构
#define CV_HAAR_FEATURE_MAX 3 /* 一个 harr 特征由 2-3 个具有相应权重的矩形组成a haar feature consists of 2-3 rectangles with appropriate weights */ typedef struct CvHaarFeature { int tilted; /* 0 means up-right feature, 1 means 45--rotated feature */ /* 2-3 rectangles with weights of opposite signs and with absolute values inversely proportional to the areas of the rectangles. if rect[2].weight !=0, then the feature consists of 3 rectangles, otherwise it consists of 2 */ struct { CvRect r; float weight; } rect[CV_HAAR_FEATURE_MAX]; } CvHaarFeature; /* a single tree classifier (stump in the simplest case) that returns the response for the feature at the particular image location (i.e. pixel sum over subrectangles of the window) and gives out a value depending on the responce */ typedef struct CvHaarClassifier { int count; /* number of nodes in the decision tree */ /* these are "parallel" arrays. Every indexi
corresponds to a node of the decision tree (root has 0-th index). left[i] - index of the left child (or negated index if the left child is a leaf) right[i] - index of the right child (or negated index if the right child is a leaf) threshold[i] - branch threshold. if feature responce is <= threshold, left branch is chosen, otherwise right branch is chosed. alpha[i] - output value correponding to the leaf. */ CvHaarFeature* haar_feature; float* threshold; int* left; int* right; float* alpha; } CvHaarClassifier; /* a boosted battery of classifiers(=stage classifier): the stage classifier returns 1 if the sum of the classifiers' responces is greater thanthreshold
and 0 otherwise */ typedef struct CvHaarStageClassifier { int count; /* number of classifiers in the battery */ float threshold; /* threshold for the boosted classifier */ CvHaarClassifier* classifier; /* array of classifiers */ /* these fields are used for organizing trees of stage classifiers, rather than just stright cascades */ int next; int child; int parent; } CvHaarStageClassifier; typedef struct CvHidHaarClassifierCascade CvHidHaarClassifierCascade; /* cascade or tree of stage classifiers */ typedef struct CvHaarClassifierCascade { int flags; /* signature */ int count; /* number of stages */ CvSize orig_window_size; /* original object size (the cascade is trained for) */ /* these two parameters are set by cvSetImagesForHaarClassifierCascade */ CvSize real_window_size; /* current object size */ double scale; /* current scale */ CvHaarStageClassifier* stage_classifier; /* array of stage classifiers */ CvHidHaarClassifierCascade* hid_cascade; /* hidden optimized representation of the cascade, created by cvSetImagesForHaarClassifierCascade */ } CvHaarClassifierCascade;
所有的结构都代表一个级联boosted Haar分类器。级联有下面的等级结构:
Cascade: Stage1: Classifier11: Feature11 Classifier12: Feature12 ... Stage2: Classifier21: Feature21 ... ...
整个等级可以手工构建,也可以利用函数cvLoadHaarClassifierCascade从已有的磁盘文件或嵌入式基中导入。
从文件中装载训练好的级联分类器或者从OpenCV中嵌入的分类器数据库中导入
CvHaarClassifierCascade* cvLoadHaarClassifierCascade( const char* directory, CvSize orig_window_size );
函数 cvLoadHaarClassifierCascade 用于从文件中装载训练好的利用海尔特征的级联分类器,或者从OpenCV中嵌入的分类器数据库中导入。分类器的训练可以应用函数haartraining(详细察看
opencv/apps/haartraining)
函数 已经过时了。现在的目标检测分类器通常存储在 XML 或 YAML 文件中,而不是通过路径导入。从文件中导入分类器,可以使用函数cvLoad 。
释放haar classifier cascade。
void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade );
函数 cvReleaseHaarClassifierCascade 释放cascade的动态内存,其中cascade的动态内存或者是手工创建,或者通过函数cvLoadHaarClassifierCascade 或 cvLoad分配。
检测图像中的目标
typedef struct CvAvgComp { CvRect rect; /* bounding rectangle for the object (average rectangle of a group) */ int neighbors; /* number of neighbor rectangles in the group */ } CvAvgComp; CvSeq* cvHaarDetectObjects( const CvArr* image, CvHaarClassifierCascade* cascade, CvMemStorage* storage, double scale_factor=1.1, int min_neighbors=3, int flags=0, CvSize min_size=cvSize(0,0) );
min_neighbors
-1 都会被排除。如果
min_neighbors
为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上。
CV_HAAR_DO_CANNY_PRUNING
。如果被设定,函数利用Canny边缘检测器来排除一些边缘很少或者很多的图像区域,因为这样的区域一般不含被检目标。人脸检测中通过设定阈值使用了这种方法,并因此提高了检测速度。
函数 cvHaarDetectObjects 使用针对某目标物体训练的级联分类器在图像中找到包含目标物体的矩形区域,并且将这些区域作为一序列的矩形框返回。函数以不同比例大小的扫描窗口对图像进行几次搜索(察看cvSetImagesForHaarClassifierCascade)。 每次都要对图像中的这些重叠区域利用cvRunHaarClassifierCascade进行检测。 有时候也会利用某些继承(heuristics)技术以减少分析的候选区域,例如利用 Canny 裁减 (prunning)方法。 函数在处理和收集到候选的方框(全部通过级联分类器各层的区域)之后,接着对这些区域进行组合并且返回一系列各个足够大的组合中的平均矩形。调节程序中的缺省参数(scale_factor
=1.1,min_neighbors
=3, flags
=0)用于对目标进行更精确同时也是耗时较长的进一步检测。为了能对视频图像进行更快的实时检测,参数设置通常是:scale_factor
=1.2, min_neighbors
=2, flags
=CV_HAAR_DO_CANNY_PRUNING, min_size
=<minimum possible face size> (例如, 对于视频会议的图像区域).
#include "cv.h" #include "highgui.h" CvHaarClassifierCascade* load_object_detector( const char* cascade_path ) { return (CvHaarClassifierCascade*)cvLoad( cascade_path ); } void detect_and_draw_objects( IplImage* image, CvHaarClassifierCascade* cascade, int do_pyramids ) { IplImage* small_image = image; CvMemStorage* storage = cvCreateMemStorage(0); CvSeq* faces; int i, scale = 1; /* if the flag is specified, down-scale the 输入图像 to get a performance boost w/o loosing quality (perhaps) */ if( do_pyramids ) { small_image = cvCreateImage( cvSize(image->width/2,image->height/2), IPL_DEPTH_8U, 3 ); cvPyrDown( image, small_image, CV_GAUSSIAN_5x5 ); scale = 2; } /* use the fastest variant */ faces = cvHaarDetectObjects( small_image, cascade, storage, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING ); /* draw all the rectangles */ for( i = 0; i < faces->total; i++ ) { /* extract the rectanlges only */ CvRect face_rect = *(CvRect*)cvGetSeqElem( faces, i, 0 ); cvRectangle( image, cvPoint(face_rect.x*scale,face_rect.y*scale), cvPoint((face_rect.x+face_rect.width)*scale, (face_rect.y+face_rect.height)*scale), CV_RGB(255,0,0), 3 ); } if( small_image != image ) cvReleaseImage( &small_image ); cvReleaseMemStorage( &storage ); } /* takes image filename and cascade path from the command line */ int main( int argc, char** argv ) { IplImage* image; if( argc==3 && (image = cvLoadImage( argv[1], 1 )) != 0 ) { CvHaarClassifierCascade* cascade = load_object_detector(argv[2]); detect_and_draw_objects( image, cascade, 1 ); cvNamedWindow( "test", 0 ); cvShowImage( "test", image ); cvWaitKey(0); cvReleaseHaarClassifierCascade( &cascade ); cvReleaseImage( &image ); } return 0; }
为隐藏的cascade(hidden cascade)指定图像
void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade, const CvArr* sum, const CvArr* sqsum, const CvArr* tilted_sum, double scale );
scale
=1, 就只用原始窗口尺寸检测 (只检测同样尺寸大小的目标物体) - 原始窗口尺寸在函数 cvLoadHaarClassifierCascade中定义 (在 "<default_face_cascade>"中缺省为24x24), 如果
scale
=2, 使用的窗口是上面的两倍 (在face cascade中缺省值是48x48 )。 这样尽管可以将检测速度提高四倍,但同时尺寸小于48x48的人脸将不能被检测到。
函数 cvSetImagesForHaarClassifierCascade 为hidden classifier cascade 指定图像 and/or 窗口比例系数。 如果图像指针为空,会继续使用原来的图像(i.e. NULLs 意味这"不改变图像")。比例系数没有 "protection" 值,但是原来的值可以通过函数cvGetHaarClassifierCascadeScale 重新得到并使用。这个函数用于对特定图像中检测特定目标尺寸的cascade分类器的设定。函数通过cvHaarDetectObjects进行内部调用,但当需要在更低一层的函数cvRunHaarClassifierCascade中使用的时候,用户也可以自行调用。
在给定位置的图像中运行 cascade of boosted classifier
int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade, CvPoint pt, int start_stage=0 );
函数 cvRunHaarHaarClassifierCascade 用于对单幅图片的检测。在函数调用前首先利用cvSetImagesForHaarClassifierCascade设定积分图和合适的比例系数 (=> 窗口尺寸)。当分析的矩形框全部通过级联分类器每一层的时返回正值(这是一个候选目标),否则返回0或负值。
对相机单精度定标
void cvCalibrateCamera( int image_count, int* point_counts, CvSize image_size, CvPoint2D32f* image_points, CvPoint3D32f* object_points, CvVect32f distortion_coeffs, CvMatr32f camera_matrix, CvVect32f translation_vectors, CvMatr32f rotation_matrixes, int use_intrinsic_guess );
函数 cvCalibrateCamera 利用目标图像模式和目标模式的象素点信息计算相机参数。
相机双精度定标
void cvCalibrateCamera_64d( int image_count, int* point_counts, CvSize image_size, CvPoint2D64d* image_points, CvPoint3D64d* object_points, CvVect64d distortion_coeffs, CvMatr64d camera_matrix, CvVect64d translation_vectors, CvMatr64d rotation_matrixes, int use_intrinsic_guess );
函数 cvCalibrateCamera_64d 跟 函数 cvCalibrateCamera用法基本相同,不过cvCalibrateCamera_64d使用的是双精度类型。
进行旋转矩阵和旋转向量间的转换,采用单精度。
void cvRodrigues( CvMat* rotation_matrix, CvMat* rotation_vector, CvMat* jacobian, int conv_type);
CV_RODRIGUES_M2V,
矩阵转化为向量。
CV_RODRIGUES_V2M
向量转化为矩阵
函数 cvRodrigues 进行旋转矩阵和旋转向量之间的相互转换。
校正相机镜头变形 Corrects camera lens distortion
void cvUnDistortOnce( const CvArr* src, CvArr* dst, const float* intrinsic_matrix, const float* distortion_coeffs, int interpolate=1 );
k1, k2, p1
和
p2
.
函数 cvUnDistortOnce 校正单幅图像的镜头变形。相机的内置参数矩阵和变形系数k1, k2 , p1
,和 p2
事先由函数cvCalibrateCamera计算得到。
计算畸变点数组和插值系数 Calculates arrays of distorted points indices and interpolation coefficients
void cvUnDistortInit( const CvArr* src, CvArr* undistortion_map, const float* intrinsic_matrix, const float* distortion_coeffs, int interpolate=1 );
interpolate=0,
与输入图像尺寸相同,如果
interpolate=1,
是输入图像的3倍
k1, k2, p1
and
p2
函数 cvUnDistortInit 利用摄像机内参数和变形系数计算畸变点指数数组和插值系数。为函数cvUnDistort计算非畸变图。
摄像机的内参数矩阵和变形系数可以由函数cvCalibrateCamera计算。
校正相机镜头变形 Corrects camera lens distortion
void cvUnDistort( const CvArr* src, CvArr* dst, const CvArr* undistortion_map, int interpolate=1 );
函数 cvUnDistort 提前计算出的未变形图校正相机镜头变形,速度比函数cvUnDistortOnce 快。(利用先前计算出的非变形图来校正摄像机的镜头变形。速度比函数 cvUnDistortOnce快)
发现棋盘内部角点的大概位置
int cvFindChessBoardCornerGuesses( const CvArr* image, CvArr* thresh, CvMemStorage* storage, CvSize board_size, CvPoint2D32f* corners, int* corner_count=NULL );
IPL_DEPTH_8U
.
函数 cvFindChessBoardCornerGuesses 试图确定输入图像是否是棋盘视图,并且确定棋盘的内部角点。如果所有角点都被发现,函数返回非零值,并且将角点按一定顺序排列(逐行由左到右),否则,函数返回零。例如一个简单棋盘有 8 x 8 方块和 7 x 7 内部方块相切的角点。单词“大概 approximate" 表示发现的角点坐标与实际坐标会有几个象素的偏差。为得到更精确的坐标,可使用函数cvFindCornerSubPix.
为模式寻找摄像机外参数矩阵
void cvFindExtrinsicCameraParams( int point_count, CvSize image_size, CvPoint2D32f* image_points, CvPoint3D32f* object_points, CvVect32f focal_length, CvPoint2D32f principal_point, CvVect32f distortion_coeffs, CvVect32f rotation_vector, CvVect32f translation_vector );
函数 cvFindExtrinsicCameraParams 寻找模式的摄像机外参数矩阵
以双精度形式寻找照相机外参数 Finds extrinsic camera parameters for pattern with double precision
void cvFindExtrinsicCameraParams_64d( int point_count, CvSize image_size, CvPoint2D64d* image_points, CvPoint3D64d* object_points, CvVect64d focal_length, CvPoint2D64d principal_point, CvVect64d distortion_coeffs, CvVect64d rotation_vector, CvVect64d translation_vector );
函数 cvFindExtrinsicCameraParams_64d 建立对象模式的外参数,双精度 。
初始化目标信息 Initializes structure containing object information
CvPOSITObject* cvCreatePOSITObject( CvPoint3D32f* points, int point_count );
函数 cvCreatePOSITObject 为对象结构分配内存并计算对象的逆矩阵。
预处理的对象数据存储在结构CvPOSITObject中,通过OpenCV内部调用,即用户不能直接得到得到数据结构。用户只可以创建这个结构并将指针传递给函数。
对象是一系列给定坐标的像素点,函数 cvPOSIT计算从照相机坐标系到目标点points[0]
之间的向量。
执行POSIT算法。Implements POSIT algorithm
void cvPOSIT( CvPOSITObject* posit_object, CvPoint2D32f* image_points, double focal_length, CvTermCriteria criteria, CvMatr32f rotation_matrix, CvVect32f translation_vector );
函数 cvPOSIT 执行POSIT算法。图像坐标在摄像机坐标系统中给出。焦距可以通过摄像机标定得到。算法每一次迭代都会重新计算在估计位置的透视投影。
两次投影之间的范式差值是对应点间的最大距离。如果差值过小,参数criteria.epsilon
就会终止程序。
释放3D对象结构
void cvReleasePOSITObject( CvPOSITObject** posit_object );
CvPOSIT
structure双指针
函数 cvReleasePOSITObject 释放函数 cvCreatePOSITObject分配的内存。
计算长方形或椭圆形平面对象的单应矩阵(例如,胳膊) Calculates homography matrix for oblong planar object (e.g. arm)
void cvCalcImageHomography( float* line, CvPoint3D32f* center, float* intrinsic, float* homography );
函数 cvCalcImageHomography 计算初始图像由图像平面到3D oblong object line界定的平面转换的单应矩阵。 (察看OpenCV指南中的3D重建一章Figure 6-10 )
计算图像中对应点的基本矩阵
int cvFindFundamentalMat( CvMat* points1, CvMat* points2, CvMat* fundamental_matrix, int method, double param1, double param2, CvMat* status=0);
外极线几何可以用下面的等式描述:
p2T*F*p1=0,
其中 F
是基本矩阵,ip1
和 p2
分别是两幅图上的对应点。
函数 FindFundamentalMat
利用上面列出的四种方法之一计算基本矩阵,并返回基本矩阵的值:没有找到矩阵,返回0,找到一个矩阵返回1,多个矩阵返回3。
基本矩阵可以用来进一步计算两幅图像的对应外极点的坐标。
7点法使用确定的7个点。这种方法能找到1个或者3个基本矩阵,返回矩阵的个数;如果目标数组中有足够的空间存储所有检测到的矩阵,该函数将所有矩阵存储,否则,只存贮其中之一。其它方法使用8点或者更多点并且返回一个基本矩阵。
int point_count = 100; CvMat* points1; CvMat* points2; CvMat* status; CvMat* fundamental_matrix; points1 = cvCreateMat(2,point_count,CV_32F); points2 = cvCreateMat(2,point_count,CV_32F); status = cvCreateMat(1,point_count,CV_32F); /* Fill the points here ... */ fundamental_matrix = cvCreateMat(3,3,CV_32F); int num = cvFindFundamentalMat(points1,points2,fundamental_matrix,CV_FM_RANSAC,1.0,0.99,status); if( num == 1 ) { printf("Fundamental matrix was found\n"); } else { printf("Fundamental matrix was not found\n"); } /*====== Example of code for three matrixes ======*/ CvMat* points1; CvMat* points2; CvMat* fundamental_matrix; points1 = cvCreateMat(2,7,CV_32F); points2 = cvCreateMat(2,7,CV_32F); /* Fill the points here... */ fundamental_matrix = cvCreateMat(9,3,CV_32F); int num = cvFindFundamentalMat(points1,points2,fundamental_matrix,CV_FM_7POINT,0,0,0); printf("Found %d matrixes\n",num);
根据一幅图像中的点计算其在另一幅图像中对应的外极线。
void cvComputeCorrespondEpilines( const CvMat* points, int which_image, const CvMat* fundamental_matrix, CvMat* correspondent_lines);
函数 ComputeCorrespondEpilines
根据外级线几何的基本方程计算每个输入点的对应外级线。如果点位于第一幅图像(which_image
=1),对应的外极线可以如下计算 :
l2=F*p1其中F是基本矩阵,
p1
是第一幅图像中的点,
l2
- 是与第二幅对应的外极线。如果点位于第二副图像中
which_image
=2),计算如下:
l1=FT*p2
其中p2
是第二幅图像中的点,l1
是对应于第一幅图像的外极线
a*x + b*y + c = 0
归一化后的外极线系数存储在correspondent_lines
.