改進的二值影像畫素標記演算法及程式實現

部落格園發表於2014-08-19

筆者實現了一個論文裡面的演算法程式,論文(可以網上搜尋到,實在搜不到可以聯絡筆者或留下郵箱發給你)講解比較到位,按照作者的思路寫完了程式碼,測試效果很好,在此分享一下演算法思路及實現程式碼。

此演算法優於一般的畫素標記演算法,只需掃描一遍就可以得出影像邊界、面積等等,大大減少了計算量。

演算法描述:

一、全圖掃描

對二值影像全圖掃描,左到右,上到下,一遇到畫素邊界就進行判斷。畫素邊界指當前畫素灰度為1,其他8領域至少有一個灰度值為0。

1.先依次判斷當前畫素(i,j)的左側、左上側、上側畫素和右上側畫素是否被已標記,一旦遇到已標記則說明當前畫素(i,j)和這個已標記畫素屬於同一個目標,賦予Edge[i][j]相同的標記值,結束本畫素標記,如四個畫素都未標記則進入第二步。

2.當前畫素右移一部,即變為(i,j+1),進入一子迴圈,每次迴圈判斷當前畫素右上側畫素是否已標記。如已標記則賦予Edge[i][j]相同的標記值並跳出迴圈結束,如當前畫素右上側畫素未標記則右移一位畫素繼續判斷,直到到達這一行畫素的右側邊界,跳出迴圈說明畫素(i,j)屬於新目標。則原來最大目標標記值temp加1並賦予Edge[i][j],結束本畫素標記。

這一大步需要注意可能會有同一類別被分到不同目標,需要全圖掃描時進行判斷,主要是凹形。

二、掃描後處理

1.歸類。前面記錄的等價標記陣列只是記錄了兩兩等價情況,而實際可能超過兩個,如三個等價。這裡需要補充的是,Same2陣列是一個tempX1的陣列,第幾行就對應第幾個目標處理情況。依次掃描Same1陣列每一行,在Same2中修改類別值,保證統一類的值歸為一類。

2.標以正確的目標值。經過上一步,屬於同一目標的畫素標記值都已歸為一類,有幾類就有幾個帶下凹的目標,再加上0的個數(不帶下凹的目標個數)就是實際目標總數。順序掃描Same2,遇到0說明該行號表示的目標位沒有下凹的,result+1賦予Same3的同一行,遇到非零數字,則看它是否第一次出現,如果第一次出現,result+1並賦予Same3同一行,如Same2這一行的值不是第一次出現,則把前面具有相同數字那一行在Same3中同行的值賦予Same3的這一行,直到檢測完Same2。最後在Same3的最後數字表示的就是目標數。

3.根據得到目標數進行目標劃分,整個影像就被分到了幾個目標值。得到的目標值可以統計目標數目、實現面積、周長和質心等特徵值。

程式程式碼:

//改進的畫素標記演算法實現程式碼及註釋
//作者用這個演算法來繪製目標外接矩形用的
//返回找到影像目標處理凹形數目,引數frame是原始二值影像,num為處理前凹形找到目標數目,s和e分別表示繪製矩形的開始點和結束點
int pixelFlag(cv::Mat &frame,int &num,vector<Point2f> &s,vector<Point2f> &e)//返回個數
{
//frame.
int kind=0,kindEnd=0,kindResult=0;//歸類類別
vector<int> same1[2];//可疑邊界目標

int edge[frame.rows][frame.cols];//表明邊界屬於哪個類
memset(edge,0,sizeof(edge));
//qDebug()<<frame.channels();
//掃描每個畫素判斷
for(int i=1;i<frame.rows-1;i++)
for(int j=1;j<frame.cols-1;j++)
{
if((frame.at<uchar>(i,j)!=0)&&(!frame.at<uchar>(i-1,j)||!frame.at<uchar>(i-1,j-1)||!frame.at<uchar>(i-1,j+1)
||!frame.at<uchar>(i,j-1)||!frame.at<uchar>(i,j+1)||!frame.at<uchar>(i+1,j-1)
||!frame.at<uchar>(i+1,j)||!frame.at<uchar>(i+1,j+1)))//判斷邊界點
{
if(edge[i][j-1])//判斷是否緊鄰已被標物體 左
{
edge[i][j]=edge[i][j-1];
}
else
if(edge[i-1][j-1])//左上
{
edge[i][j]=edge[i-1][j-1];
}
else
if(edge[i-1][j])//上
{
edge[i][j]=edge[i-1][j];
}else
if(edge[i-1][j+1])//右上
{
edge[i][j]=edge[i-1][j+1];
}else
{
int f=0;
while(frame.at<uchar>(i,j+f)&&((j+f)<frame.cols-1))//右移判斷
{
if(edge[i-1][j+f+1])
{
edge[i][j]=edge[i-1][j+f+1];
break;
}
else
{
f++;
}
}
if(!frame.at<uchar>(i,j+f))//未找到處理
{
kind++;
edge[i][j]=kind;

}
}
if(edge[i][j]&&edge[i-1][j+1])//如果當前點和右上不在一個類別就記錄
{
if(edge[i][j]!=edge[i-1][j+1])
{
same1[0].push_back(edge[i][j]);
same1[1].push_back(edge[i-1][j+1]);
}
}

 

}
}

//處理掃描後的結果
int same2[kind];memset(same2,0,sizeof(same2));
int sameEnd[kind];memset(sameEnd,0,sizeof(sameEnd));
//QDebug debug;
if(!same1[0].empty())
{
for(uint i=0;i<same1[0].size();i++)
{
if((!same2[same1[0][i]-1])&&(!same2[same1[1][i]-1]))//如果都沒有處理,種類加1
{
kindEnd++;
same2[same1[0][i]-1]=kindEnd;
same2[same1[1][i]-1]=kindEnd;
}else
if(same2[same1[0][i]-1]&&same2[same1[1][i]-1])
{
same2[same1[0][i]-1]=same2[same1[1][i]-1];

}else
if(!same2[same1[0][i]-1]&&same2[same1[1][i]-1])
{
same2[same1[0][i]-1]=same2[same1[1][i]-1];
}else if(same2[same1[0][i]-1]&&!same2[same1[1][i]-1])
{
same2[same1[1][i]-1]=same2[same1[0][i]-1];
}

}
}

for(int i=0;i<kind;i++)//複製到sameend
{

if(!same2[i])
{
kindResult++;
sameEnd[i]=kindResult;
}
else
//if(same2)
{
int j=0;
while(j<i)
{
if(same2[j]==same2[i])
{
break;
}
j++;
}
if(j<i)
{
sameEnd[i]=sameEnd[j];
}else
{
kindResult++;
sameEnd[i]=kindResult;
}

}
}
num=kind;
//對邊界進行處理
for(int i=1;i<frame.rows-1;i++)
for(int j=1;j<frame.cols-1;j++)
{
if(edge[i][j])
{
edge[i][j]=sameEnd[edge[i][j]-1];
}
}
for(int i=0;i<kindResult;i++)
{
s.push_back(Point2f(1000,1000));
e.push_back(Point2f(0,0));
}
for(int i=1;i<frame.rows-1;i++)//求邊界對角點
for(int j=1;j<frame.cols-1;j++)
{
if(edge[i][j])
{
if(s[edge[i][j]-1].y>i)
{
s[edge[i][j]-1].y=i;
}
if(s[edge[i][j]-1].x>j)
{
s[edge[i][j]-1].x=j;
}
if(e[edge[i][j]-1].y<i)
{
e[edge[i][j]-1].y=i;
}

if(e[edge[i][j]-1].x<j)
{
e[edge[i][j]-1].x=j;
}
}
}
return kindResult;

}

效果如下:

相關文章