前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Opencv求取连通区域重心实例

Opencv求取连通区域重心实例

作者头像
砸漏
发布2020-10-29 16:46:04
1.8K0
发布2020-10-29 16:46:04
举报
文章被收录于专栏:恩蓝脚本

我们有时候需要求取某一个物体重心,这里一般将图像二值化,得出该物体的轮廓,然后根据灰度重心法,计算出每一个物体的中心。

步骤如下:

1)合适的阈值二值化

2)求取轮廓

3)计算重心

otsu算法求取最佳阈值

otsu法(最大类间方差法,有时也称之为大津算法)使用的是聚类的思想,把图像的灰度数按灰度级分成2个部分,使得两个部分之间的灰度值差异最大,每个部分之间的灰度差异最小,通过方差的计算来寻找一个合适的灰度级别来划分,otsu算法被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。因此,使类间方差最大的分割意味着错分概率最小。

计算轮廓

opencv中函数findContours函数

findContours(二值化图像,轮廓,hierarchy,轮廓检索模式,轮廓近似办法,offset)

灰度重心法

利用灰度重心法计算中心,灰度重心法将区域内每一像素位置处的灰度值当做该点的“质量”,其求区域中心的公式为:

其中,f(u,v)是坐标为(u,v)的像素点的灰度值, 是目标区域集合, 是区域中心坐标,灰度重心法提取的是区域的能量中心。

代码语言:javascript
复制
//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, 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, 1, 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部分。

话不多说,上代码。

代码语言:javascript
复制
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();
}
}
//可能会存在空的轮廓,把他们删除
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求取连通区域重心实例就是小编分享给大家的全部内容了,希望能给大家一个参考。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档