Opencv求取連通區(qū)域重心實(shí)例
我們有時(shí)候需要求取某一個(gè)物體重心,這里一般將圖像二值化,得出該物體的輪廓,然后根據(jù)灰度重心法,計(jì)算出每一個(gè)物體的中心。
步驟如下:
1)合適的閾值二值化
2)求取輪廓
3)計(jì)算重心
otsu算法求取最佳閾值
otsu法(最大類間方差法,有時(shí)也稱之為大津算法)使用的是聚類的思想,把圖像的灰度數(shù)按灰度級(jí)分成2個(gè)部分,使得兩個(gè)部分之間的灰度值差異最大,每個(gè)部分之間的灰度差異最小,通過(guò)方差的計(jì)算來(lái)尋找一個(gè)合適的灰度級(jí)別來(lái)劃分,otsu算法被認(rèn)為是圖像分割中閾值選取的最佳算法,計(jì)算簡(jiǎn)單,不受圖像亮度和對(duì)比度的影響。因此,使類間方差最大的分割意味著錯(cuò)分概率最小。
計(jì)算輪廓
opencv中函數(shù)findContours函數(shù)
findContours(二值化圖像,輪廓,hierarchy,輪廓檢索模式,輪廓近似辦法,offset)
灰度重心法
利用灰度重心法計(jì)算中心,灰度重心法將區(qū)域內(nèi)每一像素位置處的灰度值當(dāng)做該點(diǎn)的“質(zhì)量”,其求區(qū)域中心的公式為:

其中,f(u,v)是坐標(biāo)為(u,v)的像素點(diǎn)的灰度值, 是目標(biāo)區(qū)域集合, 是區(qū)域中心坐標(biāo),灰度重心法提取的是區(qū)域的能量中心。
//otsu算法實(shí)現(xiàn)函數(shù)
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;
}
//統(tǒng)計(jì)灰度級(jí)中每個(gè)像素在整幅圖像中的個(gè)數(shù)
for (i = y; i < height; i++)
{
for (j = x; j<width; j++)
{
pixelCount[data[i * image.step + j]]++;
}
}
//計(jì)算每個(gè)像素在整幅圖像中的比例
for (i = 0; i < 256; i++)
{
pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum);
}
//經(jīng)典ostu算法,得到前景和背景的分割
//遍歷灰度級(jí)[0,255],計(jì)算出方差最大的灰度值,為最佳閾值
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; //整幅圖像的平均灰度
//計(jì)算類間方差
deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u);
//找出最大類間方差以及對(duì)應(yīng)的閾值
if (deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = i;
}
}
//返回最佳閾值;
return threshold;
}
int main()
{
Mat White=imread("white.tif");//讀取圖像
int threshold_white = otsu(White);//閾值計(jì)算,利用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];//假設(shè)有三個(gè)連通區(qū)域
Moments moment;//矩
vector<Point>Center;//創(chuàng)建一個(gè)向量保存重心坐標(biāo)
for (; i >= 0; i = hierarchy[i][0])//讀取每一個(gè)輪廓求取重心
{
Mat temp(contours.at(i));
Scalar color(0, 0, 255);
moment = moments(temp, false);
if (moment.m00 != 0)//除數(shù)不能為0
{
pt[i].x = cvRound(moment.m10 / moment.m00);//計(jì)算重心橫坐標(biāo)
pt[i].y = cvRound(moment.m01 / moment.m00);//計(jì)算重心縱坐標(biāo)
}
Point p = Point(pt[i].x, pt[i].y);//重心坐標(biāo)
circle(White, p, 1, color, 1, 8);//原圖畫(huà)出重心坐標(biāo)
count++;//重心點(diǎn)數(shù)或者是連通區(qū)域數(shù)
Center.push_back(p);//將重心坐標(biāo)保存到Center向量中
}
}
cout << "重心點(diǎn)個(gè)數(shù):" << Center.size() << endl;
cout << "輪廓數(shù)量:" << contours.size() << endl;
imwrite("Center.tif", White);
}
原圖:

二值化:

重心點(diǎn):

補(bǔ)充知識(shí):opencv 根據(jù)模板凸包求閾值化后的輪廓組合
圖像處理中,要求特征與背景的對(duì)比度高,同時(shí),合適的圖像分割也是解決問(wèn)題的關(guān)鍵。
博主以前的方法,默認(rèn)為特征必然是最大的連通域,所以閾值化后,查找輪廓,直接提取面積最大的輪廓即可。
但可能會(huì)存在另一種情況,不論怎么閾值化和膨脹,想要的特征被分成好幾塊,也即斷開(kāi)了。此時(shí),再加上一些不可預(yù)測(cè)的干擾和噪聲,findcontours之后,會(huì)得到很多輪廓。
那么問(wèn)題來(lái)了,我們需要的是哪個(gè)輪廓,或者是哪幾個(gè)輪廓組合的區(qū)域?
本文的意義也在于此。
根據(jù)模板的凸包,求出圖像中最相似的輪廓組合。
本方法,主要用到matchshapes函數(shù),并基于這樣一個(gè)前提:模板凸包的2/3部分,與模板凸包的相似度,大于模板凸包的1/2部分。
話不多說(shuō),上代碼。
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, contours, 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();
}
}
//可能會(huì)存在空的輪廓,把他們刪除
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[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, maxlistHull, CV_CONTOURS_MATCH_I1, 0);
double maxandseccontourScore = matchShapes(Inputlist, maxAndseclistHull, CV_CONTOURS_MATCH_I1, 0);
if (maxcontourScore>maxandseccontourScore)
{
contours[maxContourIdx].insert(contours[maxContourIdx].end(), contours[secondContourIdx].begin(), contours[secondContourIdx].end());
}
contours.erase(contours.begin() + secondContourIdx);
}
}
以上這篇Opencv求取連通區(qū)域重心實(shí)例就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
在Python中畫(huà)圖(基于Jupyter notebook的魔法函數(shù))
這篇文章主要介紹了在Python中畫(huà)圖(基于Jupyter notebook的魔法函數(shù)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
Python檢測(cè)兩個(gè)文本文件相似性的三種方法
檢測(cè)兩個(gè)文本文件的相似性是一個(gè)常見(jiàn)的任務(wù),可以用于文本去重、抄襲檢測(cè)等場(chǎng)景,Python 提供了多種方法來(lái)實(shí)現(xiàn)這一功能,x下面小編就來(lái)簡(jiǎn)單介紹一下吧2025-03-03
Python Django實(shí)現(xiàn)layui風(fēng)格+django分頁(yè)功能的例子
今天小編就為大家分享一篇Python Django實(shí)現(xiàn)layui風(fēng)格+django分頁(yè)功能的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08
python報(bào)錯(cuò)TypeError: Input z must be
大家好,本篇文章主要講的是python報(bào)錯(cuò)TypeError: Input z must be 2D, not 3D的解決方法,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2021-12-12
使用Keras預(yù)訓(xùn)練好的模型進(jìn)行目標(biāo)類別預(yù)測(cè)詳解
這篇文章主要介紹了使用Keras預(yù)訓(xùn)練好的模型進(jìn)行目標(biāo)類別預(yù)測(cè)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-06-06

