上一篇博客讨论的是由一组种子点来生长区域。另一种方法是首先将一幅图像细分为一组任意的不相交的区域,然后聚合或者分离这些区域。
这里先介绍区域分割的基本概念,虽然抽象,但仔细琢磨一下还是可以理解的。
令R表示整幅图像区域,并选择一个属性P.对R进行分割的一种方法是一次将他细分为越来越小的四象限区域,以便对于任何区域Ri,有P(Ri)=TRUE。我们从整个区域开始,如果P(Ri)=TRUE,我们把图像分割为四象限区域。若对于每个象限区域P为FALSE,则将该象限区域再细分为四个子象限区域,以此类推。这种特殊的分离技术可方便地表示为四叉树的形式,即每个节点正好有四个后代。
注意:树根M对应于整幅图像,而每个节点对应于该节点的四个细分后代节点。上图只细分了M3.
前面的讨论可以总结为如下过程:
1、将满足P(Ri)=FALSE的任何区域Ri,分离为4个不相连的象限区域
2、当不可能进一步分离时,聚合满足条件=TRUE的任意两个相邻区域Ri与Rj
3、当无法进一步聚合时停止操作
/ 代码
// 区域分裂合并的图像分割
// nOffSetLne是行偏移量
// 由于分裂的层数太多了, 使用递归将使内存空间堆栈溢出
// 解决方法是使用一个堆栈对要分裂的块入栈
// 使用堆栈的方法类似在"区域生长"的实现方法
#include <stack>
struct SplitStruct
{
unsigned int nWidth; // 这一块图像的宽度
unsigned int nHeigh; // 这一块图像的高度
unsigned int nOffSetWidth; // 相对源图像数据的偏移宽度
unsigned int nOffSetHeigh; // 相对源图像数据的偏移高度
};
void AreaSplitCombineEx(BYTE* image0, // 源图像数据
unsigned int nAllWidth, // 源图像的宽度
unsigned int nAllHeigh, // 源图像的高度
unsigned int w, // 这一块图像的宽度
unsigned int h, // 这一块图像的高度
unsigned int nOffSetWidth, // 相对源图像数据的偏移宽度
unsigned int nOffSetHeigh) // 相对源图像数据的偏移高度
{
std::stack<SplitStruct> nMyStack;
SplitStruct splitStruct, splitStructTemp;
splitStruct.nWidth = w;
splitStruct.nHeigh = h;
splitStruct.nOffSetWidth = nOffSetWidth;
splitStruct.nOffSetHeigh = nOffSetHeigh;
nMyStack.push(splitStruct);
int i, j;
int nValueS[2][2]; // 用于存储块图像的属性值(该属性值= 该块图像的所有像素灰度值之和除以该块图像所有像素点的数量)
int nAV;
int nWidthTemp[3], nHeightTemp[3], nTemp;
int nWidth, nHeigh;
int n, m, l;
double dOver;
while(!nMyStack.empty())
{
splitStruct = nMyStack.top();
nMyStack.pop();
n = (splitStruct.nOffSetHeigh * nAllWidth + splitStruct.nOffSetWidth); // 该块图像的左上角
// 1. 把图像分成2 * 2 块,
nWidthTemp[0] = 0;
nWidthTemp[2] = (splitStruct.nWidth + 1) / 2;
nWidthTemp[1] = splitStruct.nWidth - nWidthTemp[2];
nHeightTemp[0] = 0;
nHeightTemp[2] = (splitStruct.nHeigh + 1) / 2;
nHeightTemp[1] = splitStruct.nHeigh - nHeightTemp[2];
// 计算每一块图像的属性值
int nValue;
int nValueTemp;
nAV = 0;
for(i = 1; i < 3; ++i)
{
for(j = 1; j < 3; ++j)
{
nValue = 0;
m = (n + nAllWidth * nHeightTemp[i - 1] + nWidthTemp[j - 1]);
for(nHeigh = 0; nHeigh < nHeightTemp[i]; ++nHeigh)
{
for(nWidth = 0; nWidth < nWidthTemp[j]; ++nWidth)
{
l = (m + nAllWidth * nHeigh + nWidth) * 4;
nValueTemp = (0.299 * image0[l] + 0.587 * image0[l + 1] + 0.114 * image0[l + 2]);
// 灰度值之和
nValue += nValueTemp;
}
}
if(nHeightTemp[i] * nWidthTemp[j] == 0)
{
continue;
}
if(nHeightTemp[i] * nWidthTemp[j] == 1)
{
l = m * 4;
if((0.299 * image0[l] + 0.587 * image0[l + 1] + 0.114 * image0[l + 2]) < 125)
// 这个值可以动态设定
{
image0[l] = image0[l + 1] = image0[l + 2] = 0;
image0[l + 3] = 255;
}
else
{
image0[l] = image0[l + 1] = image0[l + 2] = 255;
image0[l + 3] = 255;
}
continue;
}
// 各块图像的灰度平均值(每一块图像的属性值)
nValueS[i - 1][j - 1] = nValue / (nHeightTemp[i] * nWidthTemp[j]);
// 2. 对每一块进行判断是否继续分裂(注意分裂的原则)
// 我这里的分裂原则是: 图像的属性值在属性值平均值的误差范围之内就不分裂
if(nValueS[i - 1][j - 1] < 220) // 灰度平均值少于200 需要继续分裂 // 这里就是分裂准则了
{
splitStructTemp.nWidth = nWidthTemp[j];
splitStructTemp.nHeigh = nHeightTemp[i];
splitStructTemp.nOffSetWidth = splitStruct.nOffSetWidth + nWidthTemp[j - 1];
splitStructTemp.nOffSetHeigh = splitStruct.nOffSetHeigh + nHeightTemp[i - 1];
nMyStack.push(splitStructTemp);
}
else // 合并(直接填充该块图像为黑色)
{
// 3. 如果不需要分裂, 则进行合并
for(nHeigh = 0; nHeigh < nHeightTemp[i]; ++nHeigh)
{
for(nWidth = 0; nWidth < nWidthTemp[j]; ++nWidth)
{
l = (m + nAllWidth * nHeigh + nWidth) * 4;
image0[l] = image0[l + 1] = image0[l + 2] = 255;
image0[l + 3] = 255;
}
}
}
}
}
}
return;
}
参考