Opencv求取連通區域重心例項
我們有時候需要求取某一個物體重心,這裡一般將影象二值化,得出該物體的輪廓,然後根據灰度重心法,計算出每一個物體的中心。
步驟如下:
1)合適的閾值二值化
2)求取輪廓
3)計算重心
otsu演算法求取最佳閾值
otsu法(最大類間方差法,有時也稱之為大津演算法)使用的是聚類的思想,把影象的灰度數按灰度級分成2個部分,使得兩個部分之間的灰度值差異最大,每個部分之間的灰度差異最小,通過方差的計算來尋找一個合適的灰度級別來劃分,otsu演算法被認為是影象分割中閾值選取的最佳演算法,計算簡單,不受影象亮度和對比度的影響。因此,使類間方差最大的分割意味著錯分概率最小。
計算輪廓
opencv中函式findContours函式
findContours(二值化影象,輪廓,hierarchy,輪廓檢索模式,輪廓近似辦法,offset)
灰度重心法
利用灰度重心法計算中心,灰度重心法將區域內每一畫素位置處的灰度值當做該點的“質量”,其求區域中心的公式為:
其中,f(u,v)是座標為(u,v)的畫素點的灰度值, 是目標區域集合, 是區域中心座標,灰度重心法提取的是區域的能量中心。
//otsu演算法實現函式 int Otsu(Mat &image) { int width = image.cols; int height = image.rows; int x = 0,y = 0; int pixelCount[256]; float pixelPro[256]; int i,j,pixelSum = width * height,threshold = 0; uchar* data = (uchar*)image.data; //初始化 for (i = 0; i < 256; i++) { pixelCount[i] = 0; pixelPro[i] = 0; } //統計灰度級中每個畫素在整幅影象中的個數 for (i = y; i < height; i++) { for (j = x; j<width; j++) { pixelCount[data[i * image.step + j]]++; } } //計算每個畫素在整幅影象中的比例 for (i = 0; i < 256; i++) { pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum); } //經典ostu演算法,得到前景和背景的分割 //遍歷灰度級[0,255],計算出方差最大的灰度值,為最佳閾值 float w0,w1,u0tmp,u1tmp,u0,u1,u,deltaTmp,deltaMax = 0; for (i = 0; i < 256; i++) { w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0; for (j = 0; j < 256; j++) { if (j <= i) //背景部分 { //以i為閾值分類,第一類總的概率 w0 += pixelPro[j]; u0tmp += j * pixelPro[j]; } else //前景部分 { //以i為閾值分類,第二類總的概率 w1 += pixelPro[j]; u1tmp += j * pixelPro[j]; } } u0 = u0tmp / w0; //第一類的平均灰度 u1 = u1tmp / w1; //第二類的平均灰度 u = u0tmp + u1tmp; //整幅影象的平均灰度 //計算類間方差 deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u); //找出最大類間方差以及對應的閾值 if (deltaTmp > deltaMax) { deltaMax = deltaTmp; threshold = i; } } //返回最佳閾值; return threshold; } int main() { Mat White=imread("white.tif");//讀取影象 int threshold_white = otsu(White);//閾值計算,利用otsu cout << "最佳閾值:" << threshold_white << endl; Mat thresholded = Mat::zeros(White.size(),White.type()); threshold(White,thresholded,threshold_white,255,CV_THRESH_BINARY);//二值化 vector<vector<Point>>contours; vector<Vec4i>hierarchy; findContours(thresholded,contours,hierarchy,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE);//查詢輪廓 int i = 0; int count = 0; Point pt[10];//假設有三個連通區域 Moments moment;//矩 vector<Point>Center;//建立一個向量儲存重心座標 for (; i >= 0; i = hierarchy[i][0])//讀取每一個輪廓求取重心 { Mat temp(contours.at(i)); Scalar color(0,255); moment = moments(temp,false); if (moment.m00 != 0)//除數不能為0 { pt[i].x = cvRound(moment.m10 / moment.m00);//計算重心橫座標 pt[i].y = cvRound(moment.m01 / moment.m00);//計算重心縱座標 } Point p = Point(pt[i].x,pt[i].y);//重心座標 circle(White,p,1,color,8);//原圖畫出重心座標 count++;//重心點數或者是連通區域數 Center.push_back(p);//將重心座標儲存到Center向量中 } } cout << "重心點個數:" << Center.size() << endl; cout << "輪廓數量:" << contours.size() << endl; imwrite("Center.tif",White); }
原圖:
二值化:
重心點:
補充知識:opencv 根據模板凸包求閾值化後的輪廓組合
影象處理中,要求特徵與背景的對比度高,同時,合適的影象分割也是解決問題的關鍵。
博主以前的方法,預設為特徵必然是最大的連通域,所以閾值化後,查詢輪廓,直接提取面積最大的輪廓即可。
但可能會存在另一種情況,不論怎麼閾值化和膨脹,想要的特徵被分成好幾塊,也即斷開了。此時,再加上一些不可預測的干擾和噪聲,findcontours之後,會得到很多輪廓。
那麼問題來了,我們需要的是哪個輪廓,或者是哪幾個輪廓組合的區域?
本文的意義也在於此。
根據模板的凸包,求出影象中最相似的輪廓組合。
本方法,主要用到matchshapes函式,並基於這樣一個前提:模板凸包的2/3部分,與模板凸包的相似度,大於模板凸包的1/2部分。
話不多說,上程式碼。
void getAlikeContours(std::vector<cv::Point> Inputlist,cv::Mat InputImage,std::vector<cv::Point> &Outputlist) { Mat image; InputImage.copyTo(image); vector<vector<Point> > contours; findContours(image,RETR_EXTERNAL,CHAIN_APPROX_NONE);//查詢最外層輪廓 for (int idx = contours.size() - 1; idx >= 0; idx--) { for (int i = contours[idx].size() - 1; i >= 0; i--) { if (contours[idx][i].x == 1 || contours[idx][i].y == 1 || contours[idx][i].x == image.cols - 2 || contours[idx][i].y == image.rows - 2) { swap(contours[idx][i],contours[idx][contours[idx].size() - 1]); contours[idx].pop_back(); } } //可能會存在空的輪廓,把他們刪除 for (int idx = contours.size() - 1; idx >= 0; idx--) { if (contours[idx].size() == 0) contours.erase(contours.begin() + idx); } while (true) { if (contours.size() == 0) break; if (contours.size() == 1) { vector<Point> finalList; finalList.assign(contours[0].begin(),contours[0].end()); convexHull(Mat(finalList),Outputlist,true); break; } int maxContourIdx = 0; int maxContourPtNum = 0; for (int index = contours.size() - 1; index >= 0; index--) { if (contours[index].size() > maxContourPtNum) { maxContourPtNum = contours[index].size(); maxContourIdx = index; } } //第二大輪廓 int secondContourIdx = 0; int secondContourPtNum = 0; for (int index = contours.size() - 1; index >= 0; index--) { if (index == maxContourIdx) continue; if (contours[index].size() > secondContourPtNum) { secondContourPtNum = contours[index].size(); secondContourIdx = index; } } vector<Point> maxlist; vector<Point> maxAndseclist; vector<Point> maxlistHull; vector<Point> maxAndseclistHull; maxlist.insert(maxlist.end(),contours[maxContourIdx].begin(),contours[maxContourIdx].end()); maxAndseclist.insert(maxAndseclist.end(),contours[secondContourIdx].begin(),contours[secondContourIdx].end()); convexHull(Mat(maxlist),maxlistHull,true); convexHull(Mat(maxAndseclist),maxAndseclistHull,true); double maxcontourScore = matchShapes(Inputlist,CV_CONTOURS_MATCH_I1,0); double maxandseccontourScore = matchShapes(Inputlist,0); if (maxcontourScore>maxandseccontourScore) { contours[maxContourIdx].insert(contours[maxContourIdx].end(),contours[secondContourIdx].end()); } contours.erase(contours.begin() + secondContourIdx); } }
以上這篇Opencv求取連通區域重心例項就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。