OpenCV学习(21) Grabcut算法详解

简介: grab cut算法是graph cut算法的改进。在理解grab cut算之前,应该学习一下graph cut算法的概念及实现方式。 我搜集了一些graph cut资料:http://yunpan.

grab cut算法是graph cut算法的改进。在理解grab cut算之前,应该学习一下graph cut算法的概念及实现方式。

我搜集了一些graph cut资料:http://yunpan.cn/QGDVdBXwkXutH

     grab cut算法详细描述见资料中的pdf文件:GrabCut” — Interactive Foreground Extraction using Iterated Graph Cuts

     grab cut算法是一种基于图论的图像分割方法,首先要定义一个Gibbs能量函数,然后求解这个函数的min-cut,这个min-cut就是前景背景的分割像素集合。

1. 能量函数的定义 

      在grab cut算法中,能量函数定义为:

image

      其中U函数部分表示能量函数的区域数据项,V函数表示能量函数的光滑项(边界项)。

     我们使用混合多高斯模型D(x)表示某个像素属于前景或背景的概率,这里K=5,image表示第i个单高斯函数对概率贡献的权重系数,所以有imageimage 为第i单高斯函数。image为第i个单高斯函数的均值,image为第i个单高斯函数的协方差。

image

单高斯函数g公式为:

image

 

区域数据项U函数为:

image

这里n表示图像中的第n个像素。【注:这儿的image,是对前面的D(x)取负对数】:

image

grab cut算法的输入图像是RGB 3通道的图像,对于输入图像,我们用两个混合多高斯模型来分别表示前景和背景。

 

光滑项V函数为:

image

image

C是相邻颜色对的集合,image是一个常量值50,

 

这些公式直接理解有点困难,下面我们结合程序看看grabcut算法中,如何计算能量公式,以及如何分段。

1、首先定义混合多高斯模型

class GMM
{
public:
    static const int componentsCount = 5; //对应K=5

    GMM( Mat& _model );
    double operator()( const Vec3d color ) const;
    double operator()( int ci, const Vec3d color ) const;
    int whichComponent( const Vec3d color ) const;

    void initLearning();
    void addSample( int ci, const Vec3d color );
    void endLearning();

private:
    void calcInverseCovAndDeterm( int ci );
    Mat model;
    double* coefs; //权重系数
    double* mean; //均值
    double* cov;  //协方差

    double inverseCovs[componentsCount][3][3];
    double covDeterms[componentsCount];

    double sums[componentsCount][3]; //所有样本bgr三个颜色分量的和,用来计算权重系数
    double prods[componentsCount][3][3]; //所有样本bgr颜色的行列式值,用来计算协方差
    int sampleCounts[componentsCount]; //每个单高斯函数的样本数
    int totalSampleCount;  //样本总数


};

image

image

     从上面混合多高斯公式可以知道,只要确定了三个参数:权重系数、均值、协方差,就可以根据当前像素点的bgr值确定当前像素属于前景和背景的概率D(x),所以在GMM类中,我们定义三个指针,分别表示权重系数,均值和协方差。因为当前像素用bgr值表示,所以均值其实为3个double数,再加上K=5(5个单高斯函数组成的多高斯混合函数),总共15双精度值,而权重系数则为5个双精度值,cov公共3*3*5=45个双精度值。

double* coefs; //权重系数
double* mean; //均值
double* cov; //协方差

在GMM的构造函数中,我们会创建一个1维的矩阵,总共65个双精度数,权重系数指向矩阵数据头,均值指向第6个双精度数,协方差指向第21个双精度数

    _model.create( 1, modelSize*componentsCount, CV_64FC1 );
    _model.setTo(Scalar(0));

coefs = model.ptr<double>(0);
mean = coefs + componentsCount;
cov = mean + 3*componentsCount;

2.初始化GMM变量

我们定义了两个变量

    GMM bgdGMM( bgdModel ), fgdGMM( fgdModel );

分别表示和前景的混合多高斯模型。

      首先我们根据选定的四边形框来初始化mask图像,四边形框外的像素是背景,值为GC_BGD ,四边形内的像素可能是前景,值为GC_PR_FGD。

/*
  Initialize mask using rectangular.
  设置mask的初始值,四边形框内圈定的像素值为GC_PR_FGD
*/
void gGrabCut::initMaskWithRect( Mat& mask, Size imgSize, Rect rect )
{
    mask.create( imgSize, CV_8UC1 );
    mask.setTo( GC_BGD );

    rect.x = max(0, rect.x);
    rect.y = max(0, rect.y);
    rect.width = min(rect.width, imgSize.width-rect.x);
    rect.height = min(rect.height, imgSize.height-rect.y);

    (mask(rect)).setTo( Scalar(GC_PR_FGD) );
}

      之后,我们会根据mask图像,读入样本数据。前景GMM的样本数据放在变量fgdSamples中,背景GMM的样本数据放入变量bgdSamples中。fgdSamples和bgdSamples中存放得都是一些bgr颜色值。

Mat bgdLabels, fgdLabels;
vector<Vec3f> bgdSamples, fgdSamples;
Point p;
for( p.y = 0; p.y < img.rows; p.y++ )
{
    for( p.x = 0; p.x < img.cols; p.x++ )
    {
        if( mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD )
            bgdSamples.push_back( (Vec3f)img.at<Vec3b>(p) );
        else // GC_FGD | GC_PR_FGD
            fgdSamples.push_back( (Vec3f)img.at<Vec3b>(p) );
    }
}
     之后我们会根据kmeans聚类算法,计算得到当前像素属于前景或背景混合多高斯变量中的第几个单高斯函数,结果放在bgdSamples, fgdSamples中,值为0-4。

    //一行是一个数据样本,3列是b,g,r三个属性
    Mat _bgdSamples( (int)bgdSamples.size(), 3, CV_32FC1, &bgdSamples[0][0] );
    //GMM::componentsCount聚类的个数
    //KMEANS_PP_CENTERS是采用Arthur & Vassilvitskii (2007) k-means++: The Advantages of Careful Seeding获取初始化种子点
    kmeans( _bgdSamples, GMM::componentsCount, bgdLabels,
            TermCriteria( CV_TERMCRIT_ITER, kMeansItCount, 0.0), 0, kMeansType );
    Mat _fgdSamples( (int)fgdSamples.size(), 3, CV_32FC1, &fgdSamples[0][0] );
    kmeans( _fgdSamples, GMM::componentsCount, fgdLabels,
            TermCriteria( CV_TERMCRIT_ITER, kMeansItCount, 0.0), 0, kMeansType );

下面我们计算得到均值、协方差和权重系数:

      权重系数为属于某个单高斯函数的采样像素数量除以所有采样像素的数量。每个单高斯函数的均值为所有属于该函数的采样像素颜色和除以属于该函数的颜色采样数量。协方差的公式比较复杂,大家看看下面代码中c[0]-c[9]的计算就ok了。注意其中的函数calcInverseCovAndDeterm用来计算协方差矩阵的行列式值以及逆矩阵,这些值在计算能量公式的数据项函数U时候使用。

image

//计算得到均值、协方差以及权重系数
void GMM::endLearning()
{
    const double variance = 0.01;
    for( int ci = 0; ci < componentsCount; ci++ )
    {
        int n = sampleCounts[ci];
        if( n == 0 )
            coefs[ci] = 0;
        else
        {
            coefs[ci] = (double)n/totalSampleCount;

            double* m = mean + 3*ci;
            m[0] = sums[ci][0]/n; m[1] = sums[ci][1]/n; m[2] = sums[ci][2]/n;

            double* c = cov + 9*ci;
            c[0] = prods[ci][0][0]/n - m[0]*m[0]; c[1] = prods[ci][0][1]/n - m[0]*m[1]; c[2] = prods[ci][0][2]/n - m[0]*m[2];
            c[3] = prods[ci][1][0]/n - m[1]*m[0]; c[4] = prods[ci][1][1]/n - m[1]*m[1]; c[5] = prods[ci][1][2]/n - m[1]*m[2];
            c[6] = prods[ci][2][0]/n - m[2]*m[0]; c[7] = prods[ci][2][1]/n - m[2]*m[1]; c[8] = prods[ci][2][2]/n - m[2]*m[2];

            double dtrm = c[0]*(c[4]*c[8]-c[5]*c[7]) - c[1]*(c[3]*c[8]-c[5]*c[6]) + c[2]*(c[3]*c[7]-c[4]*c[6]);
            if( dtrm <= std::numeric_limits<double>::epsilon() )
            {
               // Adds the white noise to avoid singular covariance matrix.
                c[0] += variance;
                c[4] += variance;
                c[8] += variance;
            }

            calcInverseCovAndDeterm(ci);
        }
    }
}

下面我们开始计算能量公式中光滑性函数V:

image

(注:image=1),

const double gamma = 50;
const double lambda = 9*gamma;
const double beta = calcBeta( img );

Mat leftW, upleftW, upW, uprightW;
calcNWeights( img, leftW, upleftW, upW, uprightW, beta, gamma );


     我们会在beta函数计算公式中的beta值,其中4*img.cols*img.rows - 3*img.cols - 3*img.rows + 2为邻接距离的数量。

/*
  计算光滑性函数中的beta值
  Calculate beta - parameter of GrabCut algorithm.
  beta = 1/(2*avg(sqr(||color[i] - color[j]||)))
*/
static double calcBeta( const Mat& img )
{
    double beta = 0;
    for( int y = 0; y < img.rows; y++ )
    {
        for( int x = 0; x < img.cols; x++ )
        {
            Vec3d color = img.at<Vec3b>(y,x);
            if( x>0 ) // left
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y,x-1);
                beta += diff.dot(diff);
            }
            if( y>0 && x>0 ) // upleft
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x-1);
                beta += diff.dot(diff);
            }
            if( y>0 ) // up
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x);
                beta += diff.dot(diff);
            }
            if( y>0 && x<img.cols-1) // upright
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x+1);
                beta += diff.dot(diff);
            }
        }
    }
    if( beta <= std::numeric_limits<double>::epsilon() )
        beta = 0;
    else //除以邻接距离的数量
        beta = 1.f / (2 * beta/(4*img.cols*img.rows - 3*img.cols - 3*img.rows + 2) );

    return beta;
}

      我们通过caclNWeights函数计算非终端顶点的权重值,计算公式依据V函数,权重结果放在四个矩阵leftW, upleftW, upW, uprightW中,最后,我们根据像素和权重值构建图,并用max-flow算法解得min-cut,求解的结果放在mask图像中,前景部分的值为GC_PR_FGD,背景部分的值为GC_PR_BGD

 

Mat leftW, upleftW, upW, uprightW;
calcNWeights( img, leftW, upleftW, upW, uprightW, beta, gamma );

for( int i = 0; i < iterCount; i++ )
{
    GCGraph<double> graph;
    assignGMMsComponents( img, mask, bgdGMM, fgdGMM, compIdxs );
    learnGMMs( img, mask, compIdxs, bgdGMM, fgdGMM );
    constructGCGraph(img, mask, bgdGMM, fgdGMM, lambda, leftW, upleftW, upW, uprightW, graph );
    estimateSegmentation( graph, mask );
}

相关文章
|
1月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
【EMNLP2024】基于多轮课程学习的大语言模型蒸馏算法 TAPIR
阿里云人工智能平台 PAI 与复旦大学王鹏教授团队合作,在自然语言处理顶级会议 EMNLP 2024 上发表论文《Distilling Instruction-following Abilities of Large Language Models with Task-aware Curriculum Planning》。
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
12天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
18天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。
|
6天前
|
存储 算法
基于HMM隐马尔可夫模型的金融数据预测算法matlab仿真
本项目基于HMM模型实现金融数据预测,包括模型训练与预测两部分。在MATLAB2022A上运行,通过计算状态转移和观测概率预测未来值,并绘制了预测值、真实值及预测误差的对比图。HMM模型适用于金融市场的时间序列分析,能够有效捕捉隐藏状态及其转换规律,为金融预测提供有力工具。
|
6天前
|
机器学习/深度学习 算法 信息无障碍
基于GoogleNet深度学习网络的手语识别算法matlab仿真
本项目展示了基于GoogleNet的深度学习手语识别算法,使用Matlab2022a实现。通过卷积神经网络(CNN)识别手语手势,如&quot;How are you&quot;、&quot;I am fine&quot;、&quot;I love you&quot;等。核心在于Inception模块,通过多尺度处理和1x1卷积减少计算量,提高效率。项目附带完整代码及操作视频。
|
14天前
|
算法
基于GA遗传算法的PID控制器参数优化matlab建模与仿真
本项目基于遗传算法(GA)优化PID控制器参数,通过空间状态方程构建控制对象,自定义GA的选择、交叉、变异过程,以提高PID控制性能。与使用通用GA工具箱相比,此方法更灵活、针对性强。MATLAB2022A环境下测试,展示了GA优化前后PID控制效果的显著差异。核心代码实现了遗传算法的迭代优化过程,最终通过适应度函数评估并选择了最优PID参数,显著提升了系统响应速度和稳定性。