图像孔洞填充与小连通域的删除
圖像孔洞填充與小連通域的刪除
?
cvFindContours
?從二值圖像中檢索輪廓,并返回檢測到的輪廓的個數。first_contour的值由函數填充返回,它的值將為第一個外輪廓的指針,當沒有輪廓被檢測到時為NULL。其它輪廓可以使用h_next和v_next連接,從first_contour到達。
int?cvFindContours(?CvArr*?image,?CvMemStorage*?storage,?CvSeq**?first_contour,???????????????????????????????????????????????????????????????????????????????????????????????
??????????????????int?header_size=sizeof(CvContour),?int?mode=CV_RETR_LIST,????????????????????????????????????????
??????????????????int?method=CV_CHAIN_APPROX_SIMPLE,?CvPoint?offset=cvPoint(0,0)?);?????????
image
8比特單通道的源二值圖像。非零像素作為1處理,0像素保存不變。從一個灰度圖像得到二值圖像的函數有:cvThreshold,cvAdaptiveThreshold和cvCanny。
storage
返回輪廓的容器。
first_contour
輸出參數,用于存儲指向第一個外接輪廓。
header_size
header序列的尺寸.如果選擇method?=?CV_CHAIN_CODE,?則header_size?>=?sizeof(CvChain);其他,則
header_size?>=?sizeof(CvContour)。
mode
CV_RETR_EXTERNAL:只檢索最外面的輪廓;
CV_RETR_LIST:檢索所有的輪廓,并將其放入list中;
CV_RETR_CCOMP:檢索所有的輪廓,并將他們組織為兩層:頂層是各部分的外部邊界,第二層是空洞的邊界;
CV_RETR_TREE:檢索所有的輪廓,并重構嵌套輪廓的整個層次。
?
藍色表示v_next,綠色表示h_next
method
邊緣近似方法(除了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:使用the?flavors?of?Teh-Chin?chain近似算法
的一種。
CV_LINK_RUNS:通過連接水平段的1,使用完全不同的邊緣提取算法。使用CV_RETR_LIST檢索模式能使用此方法。
offset
偏移量,用于移動所有輪廓點。當輪廓是從圖像的ROI提取的,并且需要在整個圖像中分析時,這個參數將很有用。
討論部分cvDrawContours中的案例顯示了任何使用輪廓檢測連通區域。輪廓可以用于形狀分析和目標識別——可以參考文件夾OpenCV?sample中的squares.c
?
?
cvDrawContours
cvDrawContours:在圖像上繪制外部和內部輪廓
函數cvDrawContours用于在圖像上繪制外部和內部輪廓。當thickness?>=?0?時,繪制輪廓線;否則填充由輪廓包圍的部分。
void?cvDrawContours(?CvArr?*img,?CvSeq*?contour,CvScalar?external_color,?CvScalar?hole_color,??????????????????????????????????????????????????????
??????????????????????int?max_level,?int?thickness=1,?int?line_type=8,?CvPoint?offset=cvPoint(0,0)?);???????????????????????????????????????????????????????????????????????????????????????????????????
Img?要在其上繪制輪廓的圖像。和在其他繪圖函數里一樣,輪廓是ROI的修剪結果。
Contour??指向第一個輪廓的指針。
external_color??外輪廓的顏色。
hole_color????內輪廓的顏色。??????
max_level???畫輪廓的最大層數。如果是0,只繪制contour;如果是1,將繪制contour后和contour同層的所有??
????????????輪廓;如果是2,繪制contour后所有同層和低一層的輪廓,以此類推;如果值是負值,則函數并不
????????????繪制contour后的輪廓,但是將畫出其子輪廓,一直到abs(max_level)?-?1層。
thickness
繪制輪廓線的寬度。如果為負值(例如,等于CV_FILLED),則contour內部將被繪制。
line_type
輪廓線段的類型,具體查看cvLine的描述。
offset
按給定值移動所有點的坐標。
?
?
cvContourArea
double?cvContourArea(?const?CvArr*?contour,?CvSliceslice=CV_WHOLE_SEQ?);
contour:輪廓(頂點的序列或數組)。
slice:感興趣區輪廓部分的起點和終點,默認計算整個輪廓的面積。
函數cvContourArea計算整個或部分輪廓的面積。在計算部分輪廓的情況時,由輪廓弧線和連接兩端點的弦
圍成的區域總面積被計算
?
?
?
#include <stdio.h>
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
// 內輪廓填充
// 參數:
// 1. pBinary: 輸入二值圖像,單通道,位深IPL_DEPTH_8U。
// 2. dAreaThre: 面積閾值,當內輪廓面積小于等于dAreaThre時,進行填充。
void FillInternalContours(IplImage *pBinary, double dAreaThre)
{
double dConArea;
CvSeq *pContour = NULL; //創建一序列
CvSeq *pConInner = NULL;
CvMemStorage *pStorage = NULL; //用來創建一個內存存儲器,來統一管理各種動態對象的內存,比如說序列,這個函數返回一個新創建的內存存儲器指針。
// 執行條件
if (pBinary)
{
// 查找所有輪廓
pStorage = cvCreateMemStorage(0); //創建一個內存存儲器,為0時內存塊默認大小為64k,申請一塊內存來存儲找到的輪廓序列
cvFindContours(pBinary, pStorage, &pContour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE); //從二值圖像中檢索輪廓,并返回檢測到的輪廓的個數
// 源二值圖、容器、第一個外輪廓指針、head序列大小、輪廓分兩層、邊緣只保留終點
cvDrawContours(pBinary, pContour, CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 2, CV_FILLED, 8, cvPoint(0, 0)); //在圖像上繪制外部和內部輪廓
// 源二值圖、第一個輪廓的指針、外輪廓的顏色、內輪廓的顏色、畫兩層輪廓、繪制輪廓線的寬度、輪廓線段的類型、按給定值移動所有點的坐標。
// 外輪廓循環
int wai = 0;
int nei = 0;
for (; pContour != NULL; pContour = pContour->h_next) //對檢測到的輪廓進行逐一處理,h_next為下一個輪廓
{
wai++;
// 內輪廓循環
for (pConInner = pContour->v_next; pConInner != NULL; pConInner = pConInner->h_next)
{
nei++;
dConArea = fabs(cvContourArea(pConInner, CV_WHOLE_SEQ));// 內輪廓面積
printf("%f\n", dConArea);
if (dConArea <= dAreaThre) //只處理滿足條件的輪廓,不滿足條件的輪廓扔掉
{
cvDrawContours(pBinary, pConInner, CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 0, CV_FILLED, 8, cvPoint(0, 0));
} // 源二值圖、第一個輪廓的指針、外輪廓的顏色、內輪廓的顏色、畫第一個外輪廓、繪制輪廓線的寬度、輪廓線段的類型、按給定值移動所有點的坐標。
}
}
printf("wai = %d, nei = %d", wai, nei);
cvReleaseMemStorage(&pStorage);
pStorage = NULL;
}
}
int Otsu(IplImage* src)
{
int height=src->height;
int width=src->width;
//histogram
float histogram[256] = {0};
for(int i=0; i < height; i++)
{
unsigned char* p=(unsigned char*)src->imageData + src->widthStep * i;
for(int j = 0; j < width; j++)
{
histogram[*p++]++;
}
}
//normalize histogram
int size = height * width;
for(int i = 0; i < 256; i++)
{
histogram[i] = histogram[i] / size;
}
//average pixel value
float avgValue=0;
for(int i=0; i < 256; i++)
{
avgValue += i * histogram[i]; //整幅圖像的平均灰度
}
int threshold;
float maxVariance=0;
float w = 0, u = 0;
for(int i = 0; i < 256; i++)
{
w += histogram[i]; //假設當前灰度i為閾值, 0~i 灰度的像素(假設像素值在此范圍的像素叫做前景像素) 所占整幅圖像的比例
u += i * histogram[i]; // 灰度i 之前的像素(0~i)的平均灰度值: 前景像素的平均灰度值
float t = avgValue * w - u;
float variance = t * t / (w * (1 - w) );
if(variance > maxVariance)
{
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
void DeleteIslet(IplImage* pic1,IplImage** pic0)
{
/************刪除二值化圖像中面積較小的連通域****************************/
CvSeq* contour = NULL;
double minarea =300.0;
double tmparea = 0.0;
CvMemStorage* storage = cvCreateMemStorage(0);
IplImage* img_Clone=cvCloneImage(pic1);
//訪問二值圖像每個點的值
uchar *pp;
IplImage* img_dst = cvCreateImage(cvGetSize(pic1),IPL_DEPTH_8U,1);
//------------搜索二值圖中的輪廓,并從輪廓樹中刪除面積小于某個閾值minarea的輪廓-------------//
CvScalar color = cvScalar(255,0,0);//CV_RGB(128,0,0);
CvContourScanner scanner = NULL;
scanner = cvStartFindContours(pic1,storage,sizeof(CvContour),CV_RETR_CCOMP,CV_CHAIN_APPROX_NONE,cvPoint(0,0));
//開始遍歷輪廓樹
CvRect rect;
while (contour==cvFindNextContour(scanner))
{
tmparea = fabs(cvContourArea(contour));
rect = cvBoundingRect(contour,0);
if (tmparea < minarea/*||tmparea>4900*/)
{
//當連通域的中心點為黑色時,而且面積較小則用白色進行填充
pp=(uchar*)(img_Clone->imageData + img_Clone->widthStep*(rect.y+rect.height/2)+rect.x+rect.width/2);
if (pp[0]==0)
{
for(int y = rect.y;y<rect.y+rect.height;y++)
{
for(int x =rect.x;x<rect.x+rect.width;x++)
{
pp=(uchar*)(img_Clone->imageData + img_Clone->widthStep*y+x);
if (pp[0]==0)
{
pp[0]=255;
}
}
}
}
}
}
*pic0=img_Clone;
}
int main( )
{
IplImage* oringin=cvLoadImage("test.bmp",1);
IplImage* pic1=cvLoadImage("test.bmp",0);
IplImage* pic2=NULL;
DeleteIslet(pic1,&pic2); //刪除小聯通域
IplImage* pic3=cvCloneImage(pic2); //取反再刪除
cvNot(pic2,pic3);
IplImage* pic4=NULL;
DeleteIslet(pic3,&pic4);
/**************孔洞填充*********************/
IplImage* pic5 = cvCreateImage(cvGetSize(pic4), 8, 1);
int thresh = Otsu(pic4);
cvThreshold(pic4,pic5, thresh, 255, CV_THRESH_BINARY);
FillInternalContours(pic5, 200);
IplImage* pic6 = cvCreateImage(cvGetSize(pic5), 8, 1);
cvNot(pic5,pic6);
//cvSaveImage("out2.bmp",pic6);
cvNamedWindow("oringin");
cvShowImage("oringin", oringin);
cvNamedWindow("刪除小聯通域");
cvShowImage("刪除小聯通域", pic2);
cvNamedWindow("孔洞填充");
cvShowImage("孔洞填充", pic6);
cvWaitKey(0);
cvDestroyWindow( "oringin" );
cvDestroyWindow( "刪除小聯通域" );
cvDestroyWindow( "孔洞填充" );
cvReleaseImage(&oringin);
cvReleaseImage(&pic2);
cvReleaseImage(&pic6);
return 0;
}
?
總結
以上是生活随笔為你收集整理的图像孔洞填充与小连通域的删除的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tensorflow 2.1.0 中 k
- 下一篇: 图像开运算和闭运算