图像分割实验
实验目的
二、实验环境与设备
三、实验内容
打开计算机,启动VisualC++程序建立工程,工程文件夹中应有待处理的图像文件;编程实现基于区域的图像分割,并与普通的阈值分割图像作比较;记录和整理实验报告。
四、实验原理与步骤
1.连通定义
在数字图像中,一个像素在空间上可能非常接近其它一些像素。在用网格表示的数字图像中,一个像素与其它四个像素有公共边界,并与另外四个像素共享顶角。如果两个像素有公共边界,则称它们互为4邻点(4-neighbors)。同样,如果两个像素至少共享一个顶点,则称它们互为8邻点。
连通成分标记
在一幅图像中找出连通成分是数字图像中最常见的运算之一。连通区域内的点构成表示物体的候选区域。机器视觉中的大多数物体都有表面,显然,物体表面点投影到图像平面上会形成空间上密集的点集。这里应该指出,连通成分算法常常会在二值视觉系统中形成瓶颈效应,原因是连通成分运算是一个全局性的运算,这种算法在本质上是连贯的。如果图像中仅有一个物体,那么找连通成分就没有必要;如果图像中有许多物体,且需要求出物体的特性与位置,则必须确定连通成分。
连通标记算法可以找到图像中的所有连通成分,并对同一连通成分中的所有点分配同一标记。
递归算法
算法步骤如下:
① 扫描图像,找到没有标记的1点,给它分配一个新的标记L;
② 递归分配标记L给1点的邻点;
③ 如果不存在没标记的点,则停止;
④ 返回第①步。
区域分裂算法
如果区域的某些特性不是恒定的,则区域应该分裂。基于分裂方法的图像分割过程是从最大的区域开始,在许多情况下,常把整个图像作为起始分裂的图像。
算法步骤如下:
① 形成初始区域;
② 对图像的每一个区域,连续执行下面两步:
•计算区域灰度值方差;
•如果方差值大于某一阈值,则沿着某一适合的边界分裂区域。
区域增长算法
在许多图像中,单个区域内的灰度值不是完全恒定的,因此需要更复杂的算法来进行图像分割。其中最好的算法是那些基于如下假设的算法,即图像可以划分成区域,而区域可以用简单函数模型化。
可由分割问题导出如下算法:寻找初始区域核,并从区域核开始,逐渐增长核区域,形成满足一定约束的较大的区域。
算法步骤如下:
① 把图像划分成初始区域核;
② 用平面模型拟合每一个区域核;
③ 对每一个区域,通过区域模型向邻接区域外插,求取与该区域兼容的所有点;
④ 如果没有兼容点,则增加模型阶数。如果模型阶数大于最大的模型阶数,停止区域增长;否则,回到第③步,继续区域增长;
⑤ 形成新的区域,重新用相同阶数的模型能够拟合新区域,计算拟合最佳度;
⑥ 计算区域模型的新老拟合最佳度之差;
⑦ 如果新老拟合最佳度之差小于某一给定阈值,回到第三步,继续区域增长;
⑧ 增加模型阶数,如果模型阶数大于最大的模型阶数,停止区域增长;
用新的模型阶数再拟合区域模型。入伙拟合误差增加,接收新的模型阶数,回到第③步,继续区域增长,否则,停止区域增长。
实验代码
#include
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
int T,To;//分别为迭代阈值法和otsu法计算的最佳阈值
int myotsu(Mat &src);//otsu法确定阈值函数
void RegionGrowing(Mat src,Mat &src1,Point pt);//区域生长法
void RegionCut(Mat src,Mat &src1);//区域分裂法
int main(int argc, char* argv[])
{
const char* image="C:\\Users\\dell\\Desktop\\test3.jpg";//读取图像文件
Mat img = imread(image);
if (!img.data)
{
cout << "读入图像出错" << endl;
return -1;
}
Mat src;
Mat src1(img.rows,img.cols,CV_8UC1);
Mat src2(img.rows,img.cols,CV_8UC1);
cvtColor(img, src, COLOR_BGR2GRAY);//转化为灰度图
src.copyTo(src1);
src.copyTo(src2);
To=myotsu(src);
Mat src3(src.rows,src.cols,CV_8UC1,Scalar(0));//新建一个背景全黑的图像
RegionGrowing(src,src3,Point(100,130));
Mat src4(src.rows,src.cols,CV_8UC1,Scalar(255));//新建一个背景全黑的图像
RegionCut(src,src4);
for(int i=0;i(i,j)>=128)
src.at(i,j)=255;
else
src.at(i,j)=0;
}
}
namedWindow("原图", CV_WINDOW_NORMAL);
imshow("原图",img);
namedWindow("阈值分割图(二值化)", CV_WINDOW_NORMAL);
imshow("阈值分割图(二值化)",src);
int T1=128;int T2=0;
//计算最优阈值
while(1){
int num1=0,num2=0;
int sum1=0,sum2=0;
int ave1=0,ave2=0;
for(int i=0;i(i,j)>=T1){
num1++;
sum1+=src1.at(i,j);
}else{
num2++;
sum2+=src1.at(i,j);
}
}
}
ave1=sum1/num1;
ave2=sum2/num2;
T2=(ave1+ave2)/2;
if(abs(T2-T1)<0.1){
T=T2;
break;
}else{
T1=T2;
}
}
cout<<"迭代阈值法确定的最优阈值为: "<(i,j)>=T)
src1.at(i,j)=255;
else
src1.at(i,j)=0;
}
}
namedWindow("阈值分割图(迭代阈值法)", CV_WINDOW_NORMAL);
imshow("阈值分割图(迭代阈值法)",src1);
cout<<"otsu法确定的最优阈值为: "<(i,j)>=To)
src2.at(i,j)=255;
else
src2.at(i,j)=0;
}
}
namedWindow("阈值分割图(otsu法)", CV_WINDOW_NORMAL);
imshow("阈值分割图(otsu法)",src2);
waitKey(0);
return 0;
}
int myotsu(Mat &src){
int th;
const int Gray = 256;
int pixCount[Gray] = {0};//每个灰度值所占像素个数
int pixSum = src.cols * src.rows;//图像总像素点
float pixPro[Gray] = {0};//每个灰度值所占总像素比例
float w0, w1, u0tmp, u1tmp, u0, u1, deltaTmp, deltaMax = 0;
for(int i=0;i(i,j)]++;//统计每个灰度级的像素个数
}
}
for(int i=0;i deltaMax)
{
deltaMax = deltaTmp;
th = i;
}
}
return th;
}
void RegionGrowing(Mat src,Mat &src1,Point pt){
//8邻域
Point connects[8]={Point(-1,-1),Point(0,-1),Point(1,-1),Point(-1,0),Point(1,0),Point(-1,1),Point(0,1),Point(1,1)};
int startValue=0;//生长点灰度值
int currentValue=0;//当前种子灰度值
Mat flag;//用于标记当前像素点是否被便利过
int exp=6;//可以生长的阈值范围
src1.copyTo(flag);
Point Ptnew;
stack growPt;
growPt.push(Point(pt.x,pt.y));//将种子点入栈
src1.at(Point(pt.x,pt.y))=255;//将种子点标记为白色
flag.at(pt.x,pt.y)=255;//将当前点标记为1
while(!growPt.empty()){
Point currpt=growPt.top();//将栈顶元素返回
growPt.pop();
startValue=src.at(currpt.x,currpt.y);//获取当前元素的灰度值,作为起始种子的灰度值
for(int i=0;i<8;i++){
Ptnew.x=currpt.x+connects[i].x;
Ptnew.y=currpt.y+connects[i].y;
//判断是否为边缘点
if((Ptnew.x>0) && (Ptnew.y>0) && (Ptnew.x(Ptnew.x,Ptnew.y)==0){
currentValue = src.at(Ptnew.x, Ptnew.y);
if(abs(startValue - currentValue)(Ptnew.x,Ptnew.y)=255;//将该点标记为白色
flag.at(Ptnew.x,Ptnew.y)=255;//标记该点
growPt.push(Point(Ptnew.x,Ptnew.y));//将该点入栈
}
}
}
}
}
namedWindow("区域生长法", CV_WINDOW_NORMAL);
imshow("区域生长法",src1);
}
void RegionCut(Mat src,Mat &src1){
namedWindow("huidutu", CV_WINDOW_NORMAL);
imshow("huidutu",src);
struct SplitStruct{
int width; //图像的宽度
int height; //图像的高度
int x; //相对原图像数据的偏移宽度
int y; //相对原图像数据的偏移高度
};
stack splitPt;
SplitStruct ss,ssTemp;
ss.width=src.rows;
ss.height=src.cols;
ss.x=0;
ss.y=0;
splitPt.push(ss);
int values[2][2];//存储每次分类后的区间
int Height[3],Width[3];//存储每次分裂后没个块的大小
while(!splitPt.empty()){
ss=splitPt.top();//将栈顶元素幅值给结构体
splitPt.pop(); //栈顶元素出栈
Height[0]=0;
Height[1]=(ss.height+1)/2;//第一个块的高
Height[2]=ss.height-Height[1];//第二个块的高
Width[0]=0;
Width[1]=(ss.width+1)/2; //第一个块的宽
Width[2]=ss.width-Width[1];//第二个块的宽
for(int i=1;i<3;i++){
for(int j=1;j<3;j++){
//计算每一个区域的平均灰度值
int Sumgray=0;
for(int p=0;p(p+ss.x,q+ss.y);
}
}
if(Width[i]*Height[j]==0)
continue;
if(Width[i]*Height[j]==1){ //即分到最小像素
if(src.at(Width[i-1]+ss.x,Height[j-1]+ss.y)<100){
src1.at(Width[i-1]+ss.x,Height[j-1]+ss.y)=0;
}else{
src1.at(Width[i-1]+ss.x,Height[j-1]+ss.y)=255;
}
continue;
}
values[i-1][j-1]=Sumgray/(Height[j]*Width[i]);
//判断是否需要继续分裂
for(int p=0;p(p+Width[i-1]+ss.x,q+Height[j-1]+ss.y)-values[i-1][j-1])*(src.at(p+Width[i-1]+ss.x,q+Height[j-1]+ss.y)-values[i-1][j-1]);
}
}
sn[i-1][j-1]=sngray/(Height[j]*Width[i]);
//判断是否需要继续分裂
if(sn[i-1][j-1]>16){ //如果该区域内的灰度值不满足设定阈值,则继续分裂,否则合并
//设置新的区域的大小以及相对原点像素点偏移的位置
ssTemp.width=Width[i];
ssTemp.height=Height[j];
ssTemp.x=ss.x+Width[i-1];
ssTemp.y=ss.y+Height[j-1];
splitPt.push(ssTemp); //将新的结构体元素入栈
}else{ //如果不继续分裂则合并
for(int xx=0;xx(xx+Width[i-1]+ss.x,yy+Height[j-1]+ss.y)=0; //即把该区域内的所有像素在src1中以0的灰度绘制出来
}
}
}
}
}
}
namedWindow("区域分裂合并法", CV_WINDOW_NORMAL);
imshow("区域分裂合并法",src1);
}