opencv2-第五章-图像金字塔
图像金字塔被广泛用于各种视觉应用中。图像金字塔是一个图像集合,集合中所有的图像都源于同一个原始图像,而且是通过对原始图像连续降采样获得,直到达到某个中止条件才停止降采样。(当然,降为一个像素肯定是中止条件。)
有两种类型的图像金字塔常常出现在文献和应用中:高斯金字塔和拉普拉斯金字塔。高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。
要从金字塔第i层生成第i+1层(我们表示第i+1层为Gi+1),我们先要用高斯核对Gi进行卷积,然后删除所有偶数行和偶数列。当然,新得到的图像面积会变为源图像的四分之一。按上述过程对输入图像G0循环执行操作就可产生整个金字塔。opencv为我们提供了从金字塔中上一级图像生成下一级图像的方法:
//! upsamples and smoothes the image
CV_EXPORTS_W void pyrUp( InputArray src, OutputArray dst,
const Size& dstsize=Size());
//! smooths and downsamples the image
CV_EXPORTS_W void pyrDown( InputArray src, OutputArray dst,
const Size& dstsize=Size());
我们注意到函数PyrUp并不是函数PyrDown的逆操作。之所以这样是因为PyrDown是一个会丢失信息的函数。为了恢复原来(更高的分辨率)的图像,我们需要获得由降采样操作丢失的信息,这些数据形成了拉普拉斯金字塔。
有很多操作广泛使用高斯金字塔和拉普拉斯金字塔,但一个特别重要的应用就是利用金字塔实现图像分割。图像分割需要先建立一个图像图像金字塔,然后在Gi的像素和Gi+1的像素直接依照对应关系,建立起“父-子”关系。通过这种方式,快速初始化分割可以先在金字塔高层的低分辨率图像上完成,然后逐层对分割加以优化。
opencv的函数cvPyrSegmentation实现了该算法。
PyrSegmentation
用金字塔实现图像分割
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(c1,c2)=|c1-c2|.
如果输入图像有单个通道(红、绿、兰),那么
p(c1,c2)=0,3·(c1r-c2r)+0,59·(c1g-c2g)+0,11·(c1b-c2b) .
每一个簇可以有多个连接部件。图像 src 和 dst 应该是 8-比特、单通道 或 3-通道图像,且大小一样
关于src和dst,需要特别注意一点:由于图像金字塔各层的长和宽都必须是整数,所以必须要求起始图像的长和宽都能够被2整除,并且能够被2整除的次数不少于金字塔总的层数。
指针storagee用来指向opencv的存储区。第8章将详细讨论此内容,但是现在应该知道一点,以下命令可以分配存储区域:
CvMemStorage * storage=cvCreateMemStorage();
comp参数用于存储分割结果更详细的信息--存储区里一系列相连的组成部分。具体工作机制将在第8章详细讲解。
首先,观察存储的分配情况:cvPyrSegmentation()函数从中为它要创建的连续区域申请内存。然后将指针类型设为CvSeq*,并被初始化为NULL,因为其当前值意味着无内容。我们将传递指向comp的指针给cvPyrSegmentation()函数,这样comp就可以设置为cvPyrSegmentation()创建序列的位置。调用分割操作之后,通过成员变量total,就会知道序列中的元素个数。随后,我们可以使用通用的cvGetSeqElem()以获取comp的第i个元素;然而,由于cvGetSeqElem()是通用的,所以我们必须将返回的指针强制转化为适当的类型(这里是CvConnectedComp*类型)。
最后,我们需要知道连续区域是opencv中的基本结构类型之一。可以把它视为描述图像中“团块”(blob)的一种方法。具体有一下定义:
typedef struct CvConnectedComonent{
double area; CvScalar value; CvRect rect; CvSeq* contour;};
参数area是区域的面积。参数value是区域颜色的平均值,参数rect是一个区域的外接矩形(定义在父图像的坐标系中)。最后一个参数contour是一个指向另一个序列的指针。这个序列可以用来存储区域的边界,通常是点的序列(元素类型为CvPoint)。
在cvPyrSegmentation()函数中,没有设参数contour的值。因此,如果想要区域中的元素,就只能自己计算。当然的根据自己的想法来选择函数。通常希望获得一个二值掩码图像,连续区域由掩码中的非0元素指定。可以使用连续区域的rect作为掩码,然后使用cvFloodFill()来选择矩形内所需的像素。
#include "highgui.h"
#include"cv.h"
void doPyrSegmentation( IplImage * src ,IplImage * dst)
{
assert(src->width%2 == 0 && src->height%2 == 0);
CvMemStorage * stoage = cvCreateMemStorage(0) ;
CvSeq* comp=NULL;
int level = 2 ; //进行n层采样
double threshold1 = 150 ;
double threshold2 = 30 ;
cvPyrSegmentation(src,dst, stoage,&comp,level, threshold1,threshold2) ;
};
int main(int argc,char ** argv)
{
IplImage * src = cvLoadImage(argv[1]);
IplImage * dst=cvCreateImage(cvGetSize(src), src->depth,src->nChannels);
doPyrSegmentation(src,dst);
cvNamedWindow("src") ;
cvNamedWindow("dst") ;
cvShowImage("src",src);
cvShowImage("dst",dst);
cvWaitKey(0) ;
cvDestroyAllWindows();
return 0;
}