OpenCV学习(21) Grabcut算法详解
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算法中,能量函数定义为:

其中U函数部分表示能量函数的区域数据项,V函数表示能量函数的光滑项(边界项)。
我们使用混合多高斯模型D(x)表示某个像素属于前景或背景的概率,这里K=5,
表示第i个单高斯函数对概率贡献的权重系数,所以有
,
为第i单高斯函数。
为第i个单高斯函数的均值,
为第i个单高斯函数的协方差。

单高斯函数g公式为:

区域数据项U函数为:

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

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


C是相邻颜色对的集合,
是一个常量值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; //样本总数
};


从上面混合多高斯公式可以知道,只要确定了三个参数:权重系数、均值、协方差,就可以根据当前像素点的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时候使用。

//计算得到均值、协方差以及权重系数
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:

(注:
=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 );
}
OpenCV学习(21) Grabcut算法详解的更多相关文章
- OpenCV学习(20) grabcut分割算法
http://www.cnblogs.com/mikewolf2002/p/3330390.html OpenCV学习(20) grabcut分割算法 在OpenCV中,实现了grabcut分割算法, ...
- 第三十节,目标检测算法之Fast R-CNN算法详解
Girshick, Ross. “Fast r-cnn.” Proceedings of the IEEE International Conference on Computer Vision. 2 ...
- 第二十九节,目标检测算法之R-CNN算法详解
Girshick, Ross, et al. “Rich feature hierarchies for accurate object detection and semantic segmenta ...
- 机器学习经典算法详解及Python实现--基于SMO的SVM分类器
原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector ...
- [转] KMP算法详解
转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的K ...
- KMP算法详解(转自中学生OI写的。。ORZ!)
KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句 ...
- 安全体系(一)—— DES算法详解
本文主要介绍了DES算法的步骤,包括IP置换.密钥置换.E扩展置换.S盒代替.P盒置换和末置换. 安全体系(零)—— 加解密算法.消息摘要.消息认证技术.数字签名与公钥证书 安全体系(二)——RSA算 ...
- 八大排序算法详解(动图演示 思路分析 实例代码java 复杂度分析 适用场景)
一.分类 1.内部排序和外部排序 内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程. 外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需 ...
- 第三十一节,目标检测算法之 Faster R-CNN算法详解
Ren, Shaoqing, et al. “Faster R-CNN: Towards real-time object detection with region proposal network ...
随机推荐
- Gitlab管理用户、组、权限(二)
一. 保护主要分支 保护某个分支,不让项目中其它的成员进行改变里面的内容,和不允许他们进行和保护的分支进行合并.当某个分支设置为保护的时候,那么拥有开者者权限或以下的权限都会受到影响. 执行步骤: 1 ...
- 移动端自动化测试(二)之 Appium常用的API(python)函数介绍
上一章节已经介绍了Appium的环境搭建,其实只要掌握了Appium的工作原理,前期的准备工作和安装过程是比较简单的.那么当我们搭建好Appium环境后接下来做些什么呢?通常思路是开始appium的第 ...
- Linux下的文件与目录权限
一.用户(User).群组(Group)和其他人(Others) linux是多用户多任务的操作系统,同一时刻可能会有多个用户登录系统,考虑到文件的安全性等问题,所以Linux下的文件都属于一个特定的 ...
- PHP学习日记 函数
可变函数 PHP支持可变函数的概念.所以如果一个变量后面有圆括号“()”,PHP将寻找与变量值同名的函数,并尝试执行此函数,可变函数可以用来实现回调函数.函数列表等.可变函数不能用于echo.prin ...
- thinkphp 5.0如何实现自定义404(异常处理)页面
404页面是客户端在浏览网页时,由于服务器无法正常提供信息,或是服务器无法回应,且不知道原因所返回的页面.404承载着用户体验与SEO优化的重任.404页面通常为用户访问了网站上不存在或已删除的页面, ...
- 洛谷P3521 [POI2011]ROT-Tree Rotation [线段树合并]
题目传送门 Tree Rotation 题目描述 Byteasar the gardener is growing a rare tree called Rotatus Informatikus. I ...
- Java内存是怎么管理的
JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 ( ...
- laya IDE 初始化设置
切换到 编辑模式(显示UI的界面)--按F9--- 修改 资源发布目录为 bin/
- luoguP4208 [JSOI2008]最小生成树计数 矩阵树定理
题目大意: 求最小生成树的数量 曾今的我感觉这题十分的不可做 然而今天看了看,好像是个类模板的题.... 我们十分容易知道,记能出现在最小生成树中的边的集合为\(S\) 那么,只要是\(S\)中的边构 ...
- [BZOJ1758][WC2010]重建计划(点分治+单调队列)
点分治,对于每个分治中心,考虑求出经过它的符合长度条件的链的最大权值和. 从分治中心dfs下去取出所有链,为了防止两条链属于同一个子树,我们一个子树一个子树地处理. 用s1[i]记录目前分治中心伸下去 ...
,是对前面的D(x)取负对数】: