cvMorphology形态学原理解析及源码分析
⑴?圖像形態(tài)學(xué)處理的概念...1
⑵?二值圖像的邏輯運(yùn)算...3
⑶?膨脹和腐蝕...4
(4)?高級(jí)形態(tài)學(xué)變換...8
(5)?細(xì)化...10
?
⑴?圖像形態(tài)學(xué)處理的概念
數(shù)字圖像處理中的形態(tài)學(xué)處理是指將數(shù)字形態(tài)學(xué)作為工具從圖像中提取對(duì)于表達(dá)和描繪區(qū)域形狀有用處的圖像分量,比如邊界、骨架以及凸殼,還包括用于預(yù)處理或后處理的形態(tài)學(xué)過(guò)濾、細(xì)化和修剪等。圖像形態(tài)學(xué)處理中我們感興趣的主要是二值圖像。
在二值圖像中,所有黑色像素的集合是圖像完整的形態(tài)學(xué)描述,二值圖像的各個(gè)分量是Z2的元素。假定二值圖像A和形態(tài)學(xué)處理的結(jié)構(gòu)元素B是定義在笛卡兒網(wǎng)格上的集合,網(wǎng)格中值為1的點(diǎn)是集合的元素,當(dāng)結(jié)構(gòu)元素的原點(diǎn)移到點(diǎn)(x,y)時(shí),記為Sxy,為簡(jiǎn)單起見(jiàn),結(jié)構(gòu)元素為3x3,且全都為1,在這種限制下,決定輸出結(jié)果的是邏輯運(yùn)算。
IplConvKernel結(jié)構(gòu)元素
typedef struct _IplConvKernel
{
? ? int ?nCols;?//結(jié)構(gòu)元素的行寬
? ? int ?nRows;?//列高
? ? int?anchorX; //結(jié)構(gòu)原點(diǎn)位置水平坐標(biāo)
? ? int?anchorY; //結(jié)構(gòu)原點(diǎn)位置垂直坐標(biāo)
int *values; //當(dāng)nShiftR為自定義時(shí),value是指向結(jié)構(gòu)元素?cái)?shù)據(jù)的指針
//如果結(jié)構(gòu)元素的大小定義為8*6,那么values為48長(zhǎng)的int數(shù)組,值為0或1。
? ? int?nShiftR;// 用于表示結(jié)構(gòu)元素的形狀類型
}IplConvKernel;
cvCreateStructuringElementEx創(chuàng)建結(jié)構(gòu)元素?
IplConvKernel* cvCreateStructuringElementEx( int cols, int rows, intanchor_x, int anchor_y,
? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ?? ??????????int shape, int*values=NULL );
cols 結(jié)構(gòu)元素的列數(shù)目 rows 結(jié)構(gòu)元素的行數(shù)目
anchor_x 錨點(diǎn)的相對(duì)水平偏移量 anchor_y 錨點(diǎn)的相對(duì)垂直偏移量?
shape 結(jié)構(gòu)元素的形狀,可以是下列值:?
CV_SHAPE_RECT, 長(zhǎng)方形元素;?
CV_SHAPE_CROSS, 十字交叉型,交錯(cuò)元素 a cross-shaped element;?
CV_SHAPE_ELLIPSE, 橢圓元素;?
CV_SHAPE_CUSTOM, 用戶自定義元素。這種情況下參數(shù) values 在封閉矩形內(nèi)定義核的形狀,即象素的那個(gè)鄰域必須考慮。
values 指向結(jié)構(gòu)元素的指針,它是一個(gè)平面數(shù)組,表示對(duì)元素矩陣逐行掃描。(非零點(diǎn)表示該點(diǎn)屬于結(jié)構(gòu)元)。如果指針為空,則表示平面數(shù)組中的所有元素都是非零的,即結(jié)構(gòu)元是一個(gè)長(zhǎng)方形(該參數(shù)僅僅當(dāng)shape參數(shù)是 CV_SHAPE_CUSTOM 時(shí)才予以考慮)。?
形態(tài)核與卷積核不同,不需要任何數(shù)值填充核。當(dāng)核在圖像上移動(dòng)時(shí),核的元素只需要簡(jiǎn)單表明應(yīng)該在哪個(gè)范圍內(nèi)計(jì)算最大值和最小值,參考點(diǎn)制定核與源圖像的位置關(guān)系,同時(shí)也鎖定了計(jì)算結(jié)果在目標(biāo)圖像中的位置。行和列確定了所構(gòu)造的矩形的大小(結(jié)構(gòu)元素在矩形內(nèi)),anchor_x和anchor_y是核的封閉矩形內(nèi)的參考點(diǎn)坐標(biāo)。
cvReleaseStructuringElement刪除結(jié)構(gòu)元素?
void cvReleaseStructuringElement( IplConvKernel** element );
element 被刪除的結(jié)構(gòu)元素的指針,函數(shù) cvReleaseStructuringElement 釋放結(jié)構(gòu) IplConvKernel 。如果 *element 為 NULL, 則函數(shù)不作用。
CV_IMPL IplConvKernel*
cvCreateStructuringElementEx( int cols, introws,
????????????????????????????? intanchorX, int anchorY,
????????????????????????????? intshape, int *values )
{
??? cv::Sizeksize = cv::Size(cols,rows);
??? cv::Pointanchor = cv::Point(anchorX,anchorY);
?????? // 檢測(cè)輸入數(shù)據(jù),當(dāng)用戶自定義的時(shí)候value不能為空,value默認(rèn)為NULL
??? CV_Assert(cols > 0 &&rows > 0 && anchor.inside(cv::Rect(0,0,cols,rows)) &&
?????????????? (shape!= CV_SHAPE_CUSTOM || values != 0));
?
??? int i, size = rows *cols;
??? int element_size = sizeof(IplConvKernel) +size*sizeof(int);
?????? // 為什么創(chuàng)建的內(nèi)存要比實(shí)際的大呢?大了size*sizeof(int)+32
??? IplConvKernel*element = (IplConvKernel*)cvAlloc(element_size+ 32);
?
??? element->nCols =cols;
??? element->nRows =rows;
??? element->anchorX =anchorX;
??? element->anchorY =anchorY;
//?? enum???? {??????????? CV_SHAPE_RECT????? =0,???????????? CV_SHAPE_CROSS???? =1,???????????
//????????? CV_SHAPE_ELLIPSE?? =2,???????????? CV_SHAPE_CUSTOM??? =100?????? };
??? element->nShiftR =shape< CV_SHAPE_ELLIPSE ?shape : CV_SHAPE_CUSTOM;
?????? // element指向結(jié)構(gòu)的首地址
??? element->values = (int*)(element + 1);
?????? // 如果為用戶自定義的類型,從values中取值
??? if( shape == CV_SHAPE_CUSTOM)
??? {
??????? for( i = 0; i < size; i++ )
??????????? element->values[i] =values[i];
??? }
??? else
??? {
????????????? // 根據(jù)不同的結(jié)構(gòu)類型獲得不同的數(shù)值
??????? cv::Matelem = cv::getStructuringElement(shape,ksize, anchor);
??????? for( i = 0; i < size; i++ )
??????????? element->values[i] =elem.data[i];
??? }
?
??? return element;
}
cv::Matcv::getStructuringElement(intshape, Sizeksize, Pointanchor)
{
??? int i, j;
??? int r = 0, c = 0;
??? double inv_r2 = 0;
??????
??? CV_Assert(shape ==MORPH_RECT|| shape ==MORPH_CROSS|| shape ==MORPH_ELLIPSE);
?????? //ifanchor.x=-1,anchor.x=ksize.width/2; if anchor.y=-1,anchor.y=ksize.height/2
?????? // 并判斷是否在rect(0, 0, ksize.width, ksize.height)內(nèi)
??? anchor =normalizeAnchor(anchor,ksize);
?????? // 當(dāng)只有一個(gè)結(jié)構(gòu)元素的時(shí)候cols=1 rows=1,長(zhǎng)方形結(jié)構(gòu)元素
??? if( ksize == Size(1,1))
??????? shape= MORPH_RECT;
?????? // 如果為橢圓形的結(jié)構(gòu)元素
??? if( shape == MORPH_ELLIPSE)
??? {
????????????? //r? c分別為橢圓的半徑
??????? r = ksize.height/2;
??????? c = ksize.width/2;
????????????? // 如果r!=0,inv_r2=1/(r*r)
??????? inv_r2= r ? 1./((double)r*r) : 0;
??? }
??????
??? Mat elem(ksize, CV_8U);
?
??? for( i = 0; i < ksize.height; i++ )
??? {
??????? uchar*ptr =elem.data +i*elem.step;
??????? int j1 = 0, j2 = 0;
????????????? // 根據(jù)不同的類型得到不同的起始坐標(biāo)
??????? if( shape == MORPH_RECT|| (shape ==MORPH_CROSS&& i ==anchor.y) )
??????????? j2= ksize.width;
??????? else if( shape == MORPH_CROSS )
??????????? j1= anchor.x,j2 =j1 +1;
??????? else
??????? {
??????????? intdy =i - r;
??????????? if(std::abs(dy) <=r )
??????????? {
??????????????? intdx =saturate_cast<int>(c*std::sqrt((r*r - dy*dy)*inv_r2));
??????????????? j1= std::max(c -dx, 0);
??????????????? j2= std::min(c +dx +1, ksize.width);
??????????? }
??????? }
????????????? // 小于j1的賦,大于j1小于j2的賦,其余的賦0
??????? for( j = 0; j < j1; j++ )
??????????? ptr[j] = 0;
??????? for( ; j < j2; j++ )
??????????? ptr[j] = 1;
??????? for( ; j < ksize.width;j++ )
??????????? ptr[j] = 0;
??? }
?
??? return elem;
}
⑵?二值圖像的邏輯運(yùn)算
邏輯運(yùn)算盡管本質(zhì)上很簡(jiǎn)單,但對(duì)于實(shí)現(xiàn)以形態(tài)學(xué)為基礎(chǔ)的圖像處理算法是一種有力的補(bǔ)充手段。在圖像處理中用到的主要邏輯運(yùn)算是:與、或和非(求補(bǔ)),它們可以互相組合形成其他邏輯運(yùn)算。
⑶?膨脹和腐蝕
膨脹和腐蝕這兩種操作是形態(tài)學(xué)處理的基礎(chǔ),許多形態(tài)學(xué)算法都是以這兩種運(yùn)算為基礎(chǔ)的。
①?膨脹
結(jié)構(gòu)元素B可以看作一個(gè)卷積模板,區(qū)別在于膨脹是以集合運(yùn)算為基礎(chǔ)的,卷積是以算術(shù)運(yùn)算為基礎(chǔ)的,但兩者的處理過(guò)程是相似的。
⑴?用結(jié)構(gòu)元素B,掃描圖像A的每一個(gè)像素
⑵?用結(jié)構(gòu)元素與其覆蓋的二值圖像做“與”操作
⑶?如果都為0,結(jié)果圖像的該像素為0。否則為1
cvDilate使用任意結(jié)構(gòu)元素膨脹圖像?
voidcvDilate( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, intiterations=1 );
src輸入圖像. dst 輸出圖像. element 用于膨脹的結(jié)構(gòu)元素。
若為 NULL,則使用(1+iterations*2)*(1+iterations*2)的長(zhǎng)方形的結(jié)構(gòu)元素?.iterations膨脹的次數(shù)?
函數(shù) cvDilate 對(duì)輸入圖像使用指定的結(jié)構(gòu)元進(jìn)行膨脹,該結(jié)構(gòu)決定每個(gè)具有最小值象素點(diǎn)的鄰域形狀:?dst=dilate(src,element):?dst(x,y)=max((x',y') in element))src(x+x',y+y')
函數(shù)支持(in-place)模式。膨脹可以重復(fù)進(jìn)行 (iterations) 次. 對(duì)彩色圖像,每個(gè)彩色通道單獨(dú)處理。
在試圖找到連通分支(即具有相似的顏色或強(qiáng)度的像素點(diǎn)的大塊互相分離的區(qū)域)時(shí)通常使用膨脹操作。
CV_IMPL void
cvDilate( const CvArr* srcarr, CvArr* dstarr, IplConvKernel* element,int iterations)
{
??? cv::Matsrc = cv::cvarrToMat(srcarr),dst = cv::cvarrToMat(dstarr),kernel;
?????? // 輸入輸出必須是同等尺寸、同類型的
??? CV_Assert(src.size()==dst.size()&&src.type()==dst.type());
??? cv::Pointanchor;
?????? // 若沒(méi)有結(jié)構(gòu)元素輸入,kernel=NULL,anchor=(1,1)
?????? // 否則將結(jié)構(gòu)元素中的值讀入kernel和anchor
??? convertConvKernel(element,kernel,anchor );
?????? // 邊界差值方法采用邊界復(fù)制
??? cv::dilate(src, dst, kernel, anchor, iterations,cv::BORDER_REPLICATE);
}
static voidconvertConvKernel( constIplConvKernel*src,cv::Mat&dst,cv::Point& anchor)
{
?????? // 若沒(méi)有輸入結(jié)構(gòu)元素
??? if(!src)
??? {
??????? anchor= cv::Point(1,1);
??????? dst.release();
??????? return;
??? }
?????? // 獲取結(jié)構(gòu)原點(diǎn)的坐標(biāo)
??? anchor =cv::Point(src->anchorX,src->anchorY);
?????? // 讀取結(jié)構(gòu)元素的值
??? dst.create(src->nRows,src->nCols,CV_8U);
??? int i, size = src->nRows*src->nCols;
??? for( i = 0; i < size; i++ )
??????? dst.data[i] = (uchar)src->values[i];
}
void cv::dilate( InputArraysrc, OutputArraydst, InputArraykernel,
???????????????? Pointanchor,int iterations,
???????????????? intborderType,constScalar& borderValue)
{
??? morphOp(MORPH_DILATE,src,dst, kernel,anchor, iterations,borderType, borderValue);
}
static voidmorphOp( int op, InputArray _src, OutputArray_dst,
???????????????????? InputArray_kernel,
???????????????????? Pointanchor,int iterations,
???????????????????? intborderType,constScalar& borderValue)
{
??? Mat src = _src.getMat(),kernel= _kernel.getMat();
?????? // 如果輸入的時(shí)候不輸入kernel,則kernel.data=NULL,那么ksize=(3,3)
??? Size ksize = kernel.data ?kernel.size() :Size(3,3);
?????? // ifanchor.x=-1,anchor.x=ksize.width/2; if anchor.y=-1,anchor.y=ksize.height/2
?????? // 并判斷是否在rect(0, 0, ksize.width, ksize.height)內(nèi)
??? anchor =normalizeAnchor(anchor,ksize);
?????? // 這一句是多余的,因?yàn)樵谏厦?/span>normalizeAnchor已經(jīng)判斷了
??? CV_Assert(anchor.inside(Rect(0, 0,ksize.width,ksize.height)) );
?
??? _dst.create(src.size(),src.type() );
??? Mat dst = _dst.getMat();
?????? // 如果迭代步數(shù)為或者結(jié)構(gòu)元素的尺寸為,不進(jìn)行處理,直接輸出
??? if( iterations == 0 || kernel.rows*kernel.cols == 1 )
??? {
??????? src.copyTo(dst);
??????? return;
??? }
?????? // 如果沒(méi)有輸入結(jié)構(gòu)元素,那么創(chuàng)建(1+iterations*2)*(1+iterations*2)的長(zhǎng)方形結(jié)構(gòu)元素
?????? // 結(jié)構(gòu)元素的中心點(diǎn)為(iterations, iterations),并將迭代步數(shù)設(shè)置為
??? if( !kernel.data )
??? {
??????? kernel= getStructuringElement(MORPH_RECT, Size(1+iterations*2,1+iterations*2));
??????? anchor= Point(iterations,iterations);
??????? iterations= 1;
??? }
?????? // 如果結(jié)構(gòu)步數(shù)大于的話并且kernel為長(zhǎng)方形的結(jié)構(gòu)元素,重新創(chuàng)建結(jié)構(gòu)元素
??? else if( iterations> 1 && countNonZero(kernel) == kernel.rows*kernel.cols )
??? {
??????? anchor= Point(anchor.x*iterations,anchor.y*iterations);
??????? kernel= getStructuringElement(MORPH_RECT,
?????????????????????????????????????? Size(ksize.width + (iterations-1)*(ksize.width-1),
??????????????????????????????????????????? ksize.height +(iterations-1)*(ksize.height-1)),
?????????????????????????????????????? anchor);
??????? iterations= 1;
??? }
?
int nStripes = 1;
// Tegra是NVIDIA公司于2008年推出的基于ARM構(gòu)架通用處理器品牌(即CPU,NVIDIA稱為“Computer on a chip”片上計(jì)算機(jī)),能夠?yàn)楸銛y設(shè)備提供高性能、低功耗體驗(yàn)。
#if definedHAVE_TEGRA_OPTIMIZATION
??? if (src.data != dst.data &&iterations == 1 &&? //NOTE:threads are not used for inplace processing
??????? (borderType & BORDER_ISOLATED) == 0&& //TODO: check border types
??????? src.rows >= 64 ) //NOTE: justheuristics
??????? nStripes = 4;
#endif
?
??? parallel_for_(Range(0,nStripes),
????????????????? MorphologyRunner(src,dst, nStripes,iterations,op,kernel,anchor,borderType,borderType,borderValue));
?
??? //Ptr<FilterEngine>f = createMorphologyFilter(op, src.type(),
??? //????????????????????????????????????????????kernel, anchor, borderType, borderType, borderValue );
?
??? //f->apply(src, dst );
??? //for( int i = 1;i < iterations; i++ )
??? //??? f->apply( dst, dst );
}
// 是否采用并行處理
void cv::parallel_for_(constcv::Range&range,constcv::ParallelLoopBody&body,doublenstripes=-1)
{
?????? // 大部分代碼省略,如果定義了并行框架,可以采用并行處理,一般不定義
??? (void)nstripes;
??? body(range);
}
class MorphologyRunner: public ParallelLoopBody
{
public:
??? MorphologyRunner(Mat_src, Mat _dst, int _nStripes,int _iterations,
???????????????????? int_op,Mat _kernel,Point _anchor,
???????????????????? int_rowBorderType,int_columnBorderType,constScalar& _borderValue):
??????? ????????????????????????? borderValue(_borderValue)
??? {
??????? src= _src;
??????? dst= _dst;
?
??????? nStripes= _nStripes;
??????? iterations= _iterations;
?
??????? op =_op;
??????? kernel= _kernel;
??????? anchor= _anchor;
??????? rowBorderType= _rowBorderType;
??????? columnBorderType= _columnBorderType;
??? }
?????? // ()操作符,最主要的運(yùn)算符號(hào)
??? void operator () ( const Range& range) const
??? {
??????? int row0 = min(cvRound(range.start *src.rows / nStripes),src.rows);
??????? int row1 = min(cvRound(range.end *src.rows / nStripes),src.rows);
?
??????? /*if(0)
??????????? printf("Size = (%d, %d),range[%d,%d), row0 = %d, row1 = %d\n",
?????????????????? src.rows, src.cols,range.start, range.end, row0, row1);*/
?
??????? Mat srcStripe = src.rowRange(row0,row1);
??????? Mat dstStripe = dst.rowRange(row0,row1);
????????????? ?// 創(chuàng)建形態(tài)學(xué)濾波器
??????? Ptr<FilterEngine>f= createMorphologyFilter(op,src.type(),kernel,anchor,
????????????????????????????????????????????????????rowBorderType,columnBorderType, borderValue);
????????????? // 主要的處理步驟在這里面,還未解讀
??????? f->apply(srcStripe,dstStripe );
??????? for( int i = 1; i < iterations;i++ )
??????????? f->apply(dstStripe,dstStripe );
??? }
?
private:
??? Mat src;
??? Mat dst;
??? int nStripes;
??? int iterations;
?
??? int op;
??? Mat kernel;
??? Point anchor;
??? int rowBorderType;
??? int columnBorderType;
??? Scalar borderValue;
};
②?腐蝕
對(duì)Z中的集合A和B,B對(duì)A進(jìn)行腐蝕的整個(gè)過(guò)程如下:
⑴?用結(jié)構(gòu)元素B,掃描圖像A的每一個(gè)像素
⑵?用結(jié)構(gòu)元素與其覆蓋的二值圖像做“與”操作
⑶?如果都為1,結(jié)果圖像的該像素為1。否則為0
腐蝕處理的結(jié)果是使原來(lái)的二值圖像減小一圈。
cvErode使用任意結(jié)構(gòu)元素腐蝕圖像?
void cvErode( const CvArr* src, CvArr* dst,IplConvKernel* element=NULL, int iterations=1 );
src 輸入圖像. dst 輸出圖像.element 用于腐蝕的結(jié)構(gòu)元素。
若為 NULL, 則使用 (1+iterations*2)*(1+iterations*2)的長(zhǎng)方形的結(jié)構(gòu)元素iterations 腐蝕的次數(shù)?
函數(shù) cvErode 對(duì)輸入圖像使用指定的結(jié)構(gòu)元素進(jìn)行腐蝕,該結(jié)構(gòu)元素決定每個(gè)具有最小值象素點(diǎn)的鄰域形狀:?dst=erode(src,element): ?dst(x,y)=min((x',y') inelement))src(x+x',y+y')
函數(shù)可能是本地操作支持in-place,不需另外開辟存儲(chǔ)空間的意思。腐蝕可以重復(fù)進(jìn)行 (iterations) 次. 對(duì)彩色圖像,每個(gè)彩色通道單獨(dú)處理。?
腐蝕操作通常消除圖像中的斑點(diǎn)噪聲,確保圖像中較大的區(qū)域仍然存在。
cvErode的源代碼與cvDialte的源代碼相似,在此不再對(duì)其進(jìn)行解讀
CV_IMPL void
cvErode( const CvArr* srcarr, CvArr* dstarr, IplConvKernel* element,int iterations)
{
??? cv::Matsrc = cv::cvarrToMat(srcarr),dst = cv::cvarrToMat(dstarr),kernel;
??? CV_Assert(src.size()==dst.size()&&src.type()==dst.type());
??? cv::Pointanchor;
??? convertConvKernel(element,kernel,anchor );
??? cv::erode(src, dst, kernel, anchor, iterations,cv::BORDER_REPLICATE);
}
void cv::erode( InputArraysrc, OutputArraydst, InputArraykernel,
??????????????? Pointanchor,int iterations,
??????????????? intborderType,constScalar& borderValue)
{
??? morphOp(MORPH_ERODE,src,dst, kernel,anchor, iterations,borderType, borderValue);
}
(4)?高級(jí)形態(tài)學(xué)變換
開操作是先腐蝕、后膨脹處理。
閉操作是先膨脹、后腐蝕處理。
cvMorphologyEx高級(jí)形態(tài)學(xué)變換?
void cvMorphologyEx( const CvArr* src, CvArr* dst, CvArr* temp,
? ? ? ? ? ? ? ? ? ??IplConvKernel* element, int operation, int iterations=1 );
src 輸入圖像. dst 輸出圖像. temp 臨時(shí)圖像,某些情況下需要,與源圖像同樣大小。臨時(shí)圖像 temp 在形態(tài)梯度以及對(duì)“頂帽”和“黑帽”操作時(shí)的 in-place 模式下需要。element 結(jié)構(gòu)元素,如果沒(méi)有輸入,則使用3*3的長(zhǎng)方形結(jié)構(gòu)元素。 iterations 迭代次數(shù).?
operation 形態(tài)操作的類型: CV_MOP_OPEN - 開運(yùn)算 CV_MOP_CLOSE - 閉運(yùn)算 CV_MOP_GRADIENT - 形態(tài)梯度 CV_MOP_TOPHAT - "頂帽" CV_MOP_BLACKHAT - "黑帽"?
函數(shù) cvMorphologyEx 在膨脹和腐蝕基本操作的基礎(chǔ)上,完成一些高級(jí)的形態(tài)變換:?
開運(yùn)算 dst=open(src,element)=dilate(erode(src,element),element)
開運(yùn)算通常可以用來(lái)統(tǒng)計(jì)二值圖像中的區(qū)域數(shù)。
閉運(yùn)算 dst=close(src,element)=erode(dilate(src,element),element)
在多數(shù)連通域分析方法中用閉運(yùn)算去除噪聲區(qū)域
對(duì)于連通域分析,通常先采用腐蝕或者閉運(yùn)算來(lái)消除純粹噪聲引起的部分,然后用開運(yùn)算來(lái)連接鄰近的區(qū)域。閉運(yùn)算消除低于其鄰近點(diǎn)的孤立點(diǎn),開運(yùn)算消除高于其鄰近點(diǎn)的孤立點(diǎn)。對(duì)于iterations=2,就開運(yùn)算而言其實(shí)是腐蝕->腐蝕->膨脹->膨脹這樣的過(guò)程。
形態(tài)梯度dst=morph_grad(src,element)=dilate(src,element)-erode(src,element)
對(duì)圖像進(jìn)行這一操作,可以將團(tuán)塊blob的邊緣以高亮區(qū)域突出出來(lái),保留完整的外圍邊緣。
"頂帽" dst=tophat(src,element)=src-open(src,element)?
"黑帽" dst=blackhat(src,element)=close(src,element)-src?
當(dāng)試圖孤立的部分相對(duì)于其鄰近的部分有亮度變化時(shí)可以使用,分離比鄰近的點(diǎn)亮或暗的一些斑塊。開運(yùn)算帶來(lái)的結(jié)果是放大裂縫或局部低亮度區(qū)域,因此頂帽操作可以突出與核大小相關(guān)的比源圖像周圍的區(qū)域更明亮的區(qū)域。黑帽操作突出比源圖像周圍的區(qū)域黑暗的區(qū)域。
CV_IMPL void
cvMorphologyEx( const void* srcarr,void* dstarr, void*,
??????????????? IplConvKernel*element,intop, int iterations )
{
??? cv::Matsrc = cv::cvarrToMat(srcarr),dst = cv::cvarrToMat(dstarr),kernel;
??? CV_Assert(src.size()==dst.size()&&src.type()==dst.type());
??? cv::Pointanchor;
??? IplConvKernel*temp_element =NULL;
?????? // 如果沒(méi)有給定結(jié)構(gòu)元素,則定義*3的長(zhǎng)方形元素,元素原點(diǎn)為(1,1)
??? if (!element)
??? {
??????? temp_element= cvCreateStructuringElementEx(3, 3, 1, 1, CV_SHAPE_RECT);
??? } else {
??????? temp_element= element;
??? }
?????? // 讀取結(jié)構(gòu)元素中的值
??? convertConvKernel(temp_element,kernel,anchor );
?????? // 釋放定義的結(jié)構(gòu)元素
??? if (!element)
??? {
??????? cvReleaseStructuringElement(&temp_element);
??? }
?????? // 執(zhí)行形態(tài)學(xué)操作
??? cv::morphologyEx(src,dst, op, kernel, anchor,iterations, cv::BORDER_REPLICATE );
}
void cv::morphologyEx( InputArray_src, OutputArray_dst, int op,
?????????????????????? InputArraykernel,Pointanchor,int iterations,
?????????????????????? intborderType,constScalar& borderValue)
{
??? Mat src = _src.getMat(),temp;
??? _dst.create(src.size(),src.type());
??? Mat dst = _dst.getMat();
?
??? switch( op )
??? {
??? case MORPH_ERODE:
??????? erode(src,dst,kernel,anchor,iterations,borderType,borderValue );
??????? break;
??? case MORPH_DILATE:
??????? dilate(src,dst,kernel,anchor,iterations,borderType,borderValue );
??????? break;
??? case MORPH_OPEN:
??????? erode(src,dst,kernel,anchor,iterations,borderType,borderValue );
??????? dilate(dst,dst,kernel,anchor,iterations,borderType,borderValue );
??????? break;
??? case CV_MOP_CLOSE:
??????? dilate(src,dst,kernel,anchor,iterations,borderType,borderValue );
??????? erode(dst,dst,kernel,anchor,iterations,borderType,borderValue );
??????? break;
??? case CV_MOP_GRADIENT:
??????? erode(src,temp,kernel,anchor,iterations,borderType,borderValue );
??????? dilate(src,dst,kernel,anchor,iterations,borderType,borderValue );
??????? dst-= temp;
??????? break;
??? case CV_MOP_TOPHAT:
??????? if( src.data != dst.data )
??????????? temp= dst;
??????? erode(src,temp,kernel,anchor,iterations,borderType,borderValue );
??????? dilate(temp,temp,kernel,anchor,iterations,borderType,borderValue );
??????? dst= src - temp;
??????? break;
??? case CV_MOP_BLACKHAT:
??????? if( src.data != dst.data )
??????????? temp= dst;
??????? dilate(src,temp,kernel,anchor,iterations,borderType,borderValue );
??????? erode(temp,temp,kernel,anchor,iterations,borderType,borderValue );
??????? dst= temp - src;
??????? break;
??? default:
??????? CV_Error(CV_StsBadArg,"unknownmorphological operation" );
??? }
}
(5)?細(xì)化
圖像細(xì)化一般作為一種圖像預(yù)處理技術(shù)出現(xiàn),目的是提取源圖像的骨架,即是將原圖像中線條寬度大于1個(gè)像素的線條細(xì)化成只有一個(gè)像素寬,形成“骨架”,形成骨架后能比較容易的分析圖像,如提取圖像的特征。
細(xì)化基本思想是“層層剝奪”,即從線條邊緣開始一層一層向里剝奪,直到線條剩下一個(gè)像素的為止。圖像細(xì)化大大地壓縮了原始圖像地?cái)?shù)據(jù)量,并保持其形狀的基本拓?fù)浣Y(jié)構(gòu)不變,從而為文字識(shí)別中的特征抽取等應(yīng)用奠定了基礎(chǔ)。細(xì)化算法應(yīng)滿足以下條件:
①?將條形區(qū)域變成一條薄線;
②?薄線應(yīng)位與原條形區(qū)域的中心;
③?薄線應(yīng)保持原圖像的拓?fù)涮匦浴?/p>
細(xì)化分成串行細(xì)化和并行細(xì)化,串行細(xì)化即是一邊檢測(cè)滿足細(xì)化條件的點(diǎn),一邊刪除細(xì)化點(diǎn);并行細(xì)化即是檢測(cè)細(xì)化點(diǎn)的時(shí)候不進(jìn)行點(diǎn)的刪除只進(jìn)行標(biāo)記,而在檢測(cè)完整幅圖像后一次性去除要細(xì)化的點(diǎn)。
常用的圖像細(xì)化算法有hilditch算法,pavlidis算法和rosenfeld算法等。注:進(jìn)行細(xì)化算法前要先對(duì)圖像進(jìn)行二值化,即圖像中只包含“黑”和“白”兩種顏色。
cvThin
void cvThin( IplImage* src,IplImage* dst, int iterations=1)
功能:將IPL_DEPTH_8U型二值圖像進(jìn)行細(xì)化
參數(shù):src原始IPL_DEPTH_8U型二值圖像。dst目標(biāo)存儲(chǔ)空間,必須事先分配好,且和原圖像大小類型一致。iterations,迭代次數(shù)
在opencv之前的版本中有,后來(lái)去除了
總結(jié)
以上是生活随笔為你收集整理的cvMorphology形态学原理解析及源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Wireshark抓包分析TCP建立/释
- 下一篇: 京瓷1020手动双面打印提示_自动双面打