OpenCV - 均值迭代分割

【题外话】:之前在博客中写过一篇“区域生长”的博客,区域生长在平时经常用到,也比较容易理解和代码实现,所以在很多情况下大家会选择这种方法。但是区域生长有一个最致命的点就是需要选取一个生长的种子点。
为了交流学习,同时也为了后面查阅方便,准备陆续将基于直方图的几种分割算法加以总结。

1、均值迭代算法的描述

对一幅图像 M ,均值迭代算法就是要迭代计算得到一个灰度值T,使得这个灰度值 T 将图像分成的两类A,B。满足条件: A 类的均值和B类的均值,再求均值正好等于 T
用直方图可以很直观的描述:

这里写图片描述

图示表示某个图像的灰度直方图,均值迭代就是为找到一个T,使得 T 左侧的积分面积中均值T1和右侧积分面的均值 T2 相等。

2、算法的步骤

  1. 选择一个初始化阈值 T ,通常取整张图灰度值的平均值;
  2. 计算T分成的两个部分的灰度均值 u1 u2
  3. 更新 Tu1+u2/2
  4. 重复步骤2~3,直到相邻两次计算的结果相等,或者两次结果的差值小于预先设定的值某个值;
  5. 用这个 T 对图像进行分割。

3、OpenCV下的实现

//Mat src: 待分割灰度图像
//int n:   初始阈值
void IsodataSeg(Mat &src, int n)
{
    int threshold = 0;                          //历史阈值
    int MeansO = 0;                             //前景灰度均值
    int nObject = 0;                            //实质像素点个数
    int MeansB = 0;                             //背景灰度均值
    int nBack = 0;                              //背景像素点个数
    int nCol = src.cols * src.channels();       //每行的像素个数
    while (abs(threshold - n) > 10)             //迭代停止条件
    {
        threshold = n;
        for (int i=0; i<src.rows; ++i)
        {
            uchar* pData = src.ptr<uchar>(i);
            for (int j=0; j<nCol; ++j)
            {
                if (pData[j] < threshold)   //背景
                {
                    ++nBack;
                    MeansB += pData[j];
                }
                else                        //物体
                {
                    ++nObject;
                    MeansO += pData[j];
                }
            }
        }
        if (nBack == 0 || nObject == 0)     //防止出现除以0的计算
            continue;
        n = (MeansB/nBack + MeansO/nObject) / 2;
    }
    cv::threshold(src, src, n, 255, 0);     //进行阈值分割
}

4、进一步探究

在实际的应用中常常会发现一幅图像中,我们真正想去分割的并不是整个的矩形。比如下面的图:


这里写图片描述

假设我们想要将整个奇怪的人的眼睛,嘴巴和他们脸分开,而并不关心整个图的背景部分。(当然有很多种方法可以直接去掉黑色的背景,现在仅仅讨论整个图)在算法的思路中我们可以看到,我们统计的是整个图的像素,试想如果我们不去统计哪些我们已经知道是背景颜色的像素点,那么整个问题不就转化成了我们已经能解决的问题了么?!
所以,我们仅仅在代码的实现中讲哪些我们已知是不要统计的颜色排除在外就可以了。
这里我们假定背景是纯黑的(像素值为0)。这样上述代码仅仅需要作很小的改动就能满足应用的要求了。

void IsodataSeg(Mat &src, int n)
{
    int threshold = 0;                          //历史阈值
    int MeansO = 0;                             //前景灰度均值
    int nObject = 0;                            //实质像素点个数
    int MeansB = 0;                             //背景灰度均值
    int nBack = 0;                              //背景像素点个数
    int nCol = src.cols * src.channels();       //每行的像素个数

    while (abs(threshold - n) > 10)             //迭代停止条件
    {
        threshold = n;
        for (int i=0; i<src.rows; ++i)
        {
            uchar* pData = src.ptr<uchar>(i);
            for (int j=0; j<nCol; ++j)
            {
                //黑色区域为多余的像素,不参与计算
                if (pData[j] == 0)
                    continue;

                if (pData[j] < threshold)   //背景
                {
                    ++nBack;
                    MeansB += pData[j];
                }
                else                        //物体
                {
                    ++nObject;
                    MeansO += pData[j];
                }
            }
        }
        if (nBack == 0 || nObject == 0)
            continue;
        n = (MeansB/nBack + MeansO/nObject) / 2;
    }
    cv::threshold(src, src, n, 255, 0);                             //进行阈值分割
}

5、均值迭代在哪好用

从算法的描述步骤中可以看到,均值迭代算法的收敛速度是很快的。也就意味了在合适的场景下,使用均值迭代算法是具有绝对优势的。但是也需要注意一点均值迭代一般比较使用直方图为典型的“双峰”的图像。这一特征也是基于直方图统计类算法的共同特征。
可以体会到均值迭代算法其实是一种简单的聚类,它每次将样本分成两类,两类的均值作为两类的聚类中心,每一次计算的 T 与两类均值的距离为聚类的半径。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
均值漂移图像分割是一种基于密度的非参数化图像分割方法,可以自动地将图像分成不同的区域。该算法的核心思想是将图像中的每个像素点看作一个样本,然后通过代计算样本点的密度中心来实现区域的合并。均值漂移算法可以处理图像中的多个物体,对噪声和图像的大部分变换都具有很好的鲁棒性。 以下是均值漂移图像分割的基本步骤: 1. 对输入图像进行滤波处理,以去除一些高频噪声。 2. 选择一个种子点,并以该点为中心,计算该点周围的所有像素点的密度中心。 3. 以密度中心为中心,重新计算该点周围的所有像素点的密度中心。 4. 当密度中心的偏移量小于预设的阈值时,就认为该区域已经稳定,将该区域中的像素点赋予相同的标记值。 5. 选择一个新的未被分割的像素点作为种子点,重复上述过程,直到所有像素点都被标记为止。 以下是基于OpenCV实现均值漂移图像分割的示例代码: ```c++ #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // 读入图像 Mat src = imread("test.jpg"); // 显示原始图像 imshow("src", src); // 进行均值漂移分割 Mat dst; pyrMeanShiftFiltering(src, dst, 10, 20); // 显示分割结果 imshow("dst", dst); waitKey(0); return 0; } ``` 其中,`src`表示原始图像,`dst`表示目标图像,`pyrMeanShiftFiltering`函数实现了均值漂移分割。在函数调用中,第三个参数表示空间窗口半径,第四个参数表示色彩空间窗口半径。这两个参数的取值会影响到分割的结果,需要根据具体情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空空的司马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值