1、区域生长法
区域生长法是一种基于广度优先的分割方法,实现方法如下:
/**
*@brief 区域生长法,输入图像应为灰度图
*@para srcImg 区域生长原图像
*@para pt 种子点
*@para thre 阈值
**/
void RegionGrowing(Mat srcImg, Mat& dstImg, Point pt, int thre)
{
// Mat RegionGrowing(Mat srcImg, Point pt, int thre)
// return growImage.clone();
Point ptGrowing; //待生长点坐标
int nGrowLabel = 0; //是否被标记 markImage灰度值不为0
int startPtValue = 0; //生长起始点灰度值
int currPtValue = 0; //当前生长点灰度值
//int growPtValue = 0; //待生长点灰度值
Mat markImg = Mat::zeros(srcImg.size(), CV_8UC1);//创建一个空白区域,填充颜色为黑色
int mDir[8][2] = { { -1,-1 },{ 0,-1 },{ 1,-1 },{ -1,0 },{ 1,0 },{ -1,1 },{ 0,1 },{ 1,1 } }; //8邻域
vector<Point> growPtVec;//生长点栈
growPtVec.push_back(pt);//将初始生长点压入栈
//unsigned char *pData = (unsigned char *)(markImg.data + pt.y*markImg.step);
//pData[pt.x] = 255;//标记初始生长点
markImg.at<uchar>(pt) = 255;
//startPtValue = ((unsigned char*)(srcImg.data + pt.y*srcImg.step))[pt.x];//该像素点所在行的首地址,然后再加上该像素点所在的列
startPtValue = srcImg.at<uchar>(pt);
while (!growPtVec.empty())
{
Point currPt = growPtVec.back(); //返回当前vector最末一个元素
growPtVec.pop_back(); //弹出最后压入的数据
for (int i = 0; i < 8; i++)
{
ptGrowing.x = currPt.x + mDir[i][0];
ptGrowing.y = currPt.y + mDir[i][1];
//判断是否是边缘点
if (ptGrowing.x < 0 || ptGrowing.y < 0 || (ptGrowing.x > srcImg.cols - 1) || (ptGrowing.y > srcImg.rows - 1))
continue;//继续执行下一次循环
//判断是否已被标记
//nGrowLabel = ((unsigned char*)(markImg.data + ptGrowing.y*markImg.step))[ptGrowing.x];
nGrowLabel = markImg.at<uchar>(ptGrowing);
if (nGrowLabel == 0) //没有被标记
{
//currPtValue = ((unsigned char*)(srcImg.data + ptGrowing.y*srcImg.step))[ptGrowing.x];
//currPtValue = srcImg.at<uchar>(currPt.y, currPt.x);
currPtValue = srcImg.at<uchar>(ptGrowing);
if (abs(currPtValue - startPtValue) <= thre)
{
//((unsigned char*)(markImg.data + ptGrowing.y*markImg.step))[ptGrowing.x] = 255;
markImg.at<uchar>(ptGrowing) = 255;
growPtVec.push_back(ptGrowing);
}
}
}
}
markImg.copyTo(dstImg);
}
接下来是基于阈值的分割方法
2、均值迭代分割
上图是一个图像的灰度直方图,那么均值迭代的目标就是要找一个阈值,使两边的均值相等。均值迭代一般比较使用直方图为典型的“双峰”的图像
算法步骤:
1、设定一个初始化阈值T
2、计算两部分的灰度均值u1,u2.(这边很像OTSU)
3、更新T=(u1 + u2)/ 2,重复2,3
4、使用T来分割图像
void MeanIteration(Mat& img, int n) {
int thres = 0;
int nCol = img.cols*img.channels();
while (abs(thres - n) > 10)
{
int meanO = 0;
int nObject = 0;
int meanB = 0;
int nBack = 0;
thres = n;
for (int i = 0; i < img.rows; i++)
{
uchar* pData = img.ptr<uchar>(i);
for (int j = 0; j < nCol; j++)
{
if (pData[j] < thres)
{
++nBack;
meanB += pData[j];
}
else
{
++nObject;
meanO += pData[j];
}
}
}
if (nBack == 0 || nObject == 0)
continue;
n = (meanB / nBack + meanO / nObject) / 2;
}
threshold(img, img, n, 255, 0);
}
3、OTSU分割算法 - 最大类间方差
https://blog.csdn.net/qq_41828351/article/details/88718246
4、最大熵分割
最大熵分割:公式参考https://blog.csdn.net/Robin__Chou/article/details/53931442
和上面集中方法做比较来看,其实就是用不同的方法来计算阈值的。(代码和OTSU的实现方式相同)
Mat entropySeg(Mat& src)
{
int tbHist[256] = { 0 }; //每个像素值个数
int index = 0; //最大熵对应的灰度
double Property = 0.0; //像素所占概率
double maxEntropy = -1.0; //最大熵
double frontEntropy = 0.0; //前景熵
double backEntropy = 0.0; //背景熵
//纳入计算的总像素数
int TotalPixel = 0;
int nCol = src.cols * src.channels(); //每行的像素个数
for (int i = 0; i < src.rows; i++)
{
uchar* pData = src.ptr<uchar>(i);
for (int j = 0; j < nCol; ++j)
{
++TotalPixel;
tbHist[pData[j]] += 1;
}
}
for (int i = 0; i < 256; i++)
{
//计算背景像素数
double backTotal = 0;
for (int j = 0; j < i; j++)
{
backTotal += tbHist[j];
}
//背景熵
for (int j = 0; j < i; j++)
{
if (tbHist[j] != 0)
{
Property = tbHist[j] / backTotal;
backEntropy += -Property * logf((float)Property);
}
}
//前景熵
for (int k = i; k < 256; k++)
{
if (tbHist[k] != 0)
{
Property = tbHist[k] / (TotalPixel - backTotal);
frontEntropy += -Property * logf((float)Property);
}
}
if (frontEntropy + backEntropy > maxEntropy) //得到最大熵
{
maxEntropy = frontEntropy + backEntropy;
index = i;
}
//清空本次计算熵值
frontEntropy = 0.0;
backEntropy = 0.0;
}
Mat dst;
//index += 20;
cv::threshold(src, dst, index, 255, 0); //进行阈值分割
return dst.clone();
}