图像分割和轮廓提取

基于幅度的阈值分割方法

直接固定阈值法

就是选择一个阈值,对图像进行二值化处理,如果当图像中的像素值小于该阈值时,可以置零或255,相反的,当图像中的像素值大于该阈值时,可以置255或0. 总之,图像分割后的图像是二值的,就是只有0和255.

自适应阈值法

基本思路就是,对图像中的每个像素,选取以它为中心的一个领域窗口(比如8邻域窗口),对这个窗口的像素灰度按照一定的准则来选取阈值,以此判断该处理像素是属于目标还是背景。而选取阈值的方法还有很多的,比如可以选择窗口中各像素的灰度的最大值和最小值的均值,或窗口内所有像素灰度的均值等。

基于区域生长的阈值分割方法

主要就是有一个生长门限,然后把当前点和它的四邻域点相比较,如果四邻域点中有和当前点的灰度值相差小于生长门限的,那么这个点将会成为下一个生长的起始点。当然,这个生长的起始点我们都叫做种子点,如果种子点选取的不适当的话,生长的结果将会差别很大的。后面的截图中会有更加明显的区别。

Visual C++6.0的程序实现

直接固定阈值法

可以写出下面的函数:
/******************************************************************************
*函数功能:实现固定阈值分割算法
*函数声明:
   BOOL FixedThreshold(
    BYTE* image,  -指向源图像的像素数据的指针
    LONG imageWidth, -源图像的宽度
    LONG imageHeight, -源图像的高度
    int threshold,  -分割门限
    int nBitCount  -源图像的位数
    )
******************************************************************************/
BOOL FixedThreshold(BYTE* image,LONG imageWidth,LONG imageHeight,int threshold,
     int nBitCount=8)
{
 if(nBitCount==8)
 {
  for(int i=0;i<imageHeight;i++)
  {
   for(int j=0;j<imageWidth;j++)
   {
    BYTE gray=*(image+imageWidth*i+j);
    if(gray<=threshold)
    {
     *(image+imageWidth*i+j)=0;
    }
    else
    {
     *(image+imageWidth*i+j)=255;
    }
   }
  }
 }
 else
 {
  AfxMessageBox("暂时只能处理8位位图");
  return false;
 }
 return true;
}

自适应阈值分割法

程序如下:
/******************************************************************************
*函数功能:实现自适应阈值分割算法
*函数声明:
   BOOL AutoThreshold(
    BYTE* image,  -指向源图像的像素数据的指针
    LONG imageWidth, -源图像的宽度
    LONG imageHeight, -源图像的高度
    int nBitCount  -源图像的位数
   )
******************************************************************************/
BOOL AutoThreshold(BYTE* image,LONG imageWidth,LONG imageHeight,int nBitCount=8)
{
 if(nBitCount==8)
 {
  //新建一个缓存的图像内存
  BYTE* dst=new BYTE[imageWidth*imageHeight];
  memset(dst,255,imageWidth*imageHeight);
  
  //保存像素点的8个相邻的像素点的值
  BYTE nw,n,ne,w,e,sw,s,se;
  //保存3×3窗口中所有元素的均值
  int avg;
  //保存处理的像素点的地址
  unsigned char* src;
  //为防止越界,除去图形的上下左右各1列
  //采用的是8邻域窗口,取8个像素的均值作为处理点的门限
  for(int i=1;i<imageHeight-1;i++)
  {
   avg=0;
   for(int j=1;j<imageWidth-1;j++)
   {
    src=image+imageWidth*i+j;
    BYTE gray=*(src);  //要处理的像素点
    n=*(src+imageWidth); //上邻点
    nw=*(src+imageWidth-1); //左上邻点
    ne=*(src+imageWidth+1); //右上邻点
    w=*(src-1);    //左邻点
    e=*(src+1);    //右邻点
    s=*(src-imageWidth); //下邻点
    sw=*(src-imageWidth-1); //左下邻点
    se=*(src-imageWidth+1); //右下邻点
    avg=(n+nw+ne+w+e+s+sw+se)/8;
    //判断当前点的像素值
    if(gray<=avg)
    {
     *(dst+imageWidth*i+j)=0;
    }
   }
  }
  memcpy(image,dst,imageWidth*imageHeight);
  delete[] dst;
 }
 else
 {
  AfxMessageBox("只能处理8位位图");
  return false;
 }
 return true;
}

区域生长的分割法

/******************************************************************************
*函数功能:实现区域生长的分割算法
*函数声明:
   BOOL RegionGrow(
    BYTE* srcImage,  -指向源图像的像素数据的指针
    LONG imageWidth, -源图像的宽度
    LONG imageHeight, -源图像的高度
    int nThreshold,  -区域生长的门限
    int seedx,   -区域生长种子的横坐标
    int seedy,   -区域生长种子的纵坐标
    int mode,   -区域生长种子的选取类型
    int nBitCount  -源图像的位数
    )
******************************************************************************/
BOOL RegionGrow(BYTE* srcImage,LONG imageWidth,LONG imageHeight,int nThreshold,
    int seedx,int seedy,int mode=0,int nBitCount=8)
{
 //设置用于处理思邻域的数组,便于下面的循环运算
 static int Dx[]={-1,0,1,0};
 static int Dy[]={0,1,0,-1};
 if(nBitCount==8)
 {
  //新分配一块内存
  BYTE* dstImage=new BYTE[imageHeight*imageWidth];
  //全部置255(白色)
  memset(dstImage,255,imageHeight*imageWidth);
  //判断是何种种子选取类型
  CPoint seed;
  if(mode==0)
  {
   //默认以图像中心为种子点
   seed.x=imageWidth/2;
   seed.y=imageHeight/2;
  }
  else
  {
   //自定义种子点
   if(seedx>imageWidth || seedy>imageHeight)
   {
    AfxMessageBox("种子点在图像之外");
    return false;
   }
   else
   {
    seed.x=seedx;
    seed.y=seedy; 
   }
  }
  //定义存储X坐标的堆栈
  int* GrowX=new int[imageHeight*imageWidth];
  //定义存储Y坐标的堆栈
  int* GrowY=new int[imageWidth*imageHeight];
  //定义堆栈的起始点
  int start=0;
  //定义堆栈的终止点
  int end=0;
  GrowX[end]=seed.x;
  GrowY[end]=seed.y;
  CPoint current;
  int xx,yy;
  while(start<=end)
  {
   current.x=GrowX[start];
   current.y=GrowY[start];
   //对当前点的四邻域进行判断
   for(int k=0;k<4;k++)
   {
    xx=current.x+Dx[k];
    yy=current.y+Dy[k];
    unsigned char* lpSrc=srcImage+imageWidth*(imageHeight-yy)+xx;
    unsigned char* lpSrc1=srcImage+imageWidth*(imageHeight-current.y)+current.x;
    unsigned char* lpDst=dstImage+imageWidth*(imageHeight-yy)+xx;
    if(xx<imageWidth && xx>=0 && yy<imageHeight && yy>=0
     && *lpDst==255 && abs(*lpSrc-*lpSrc1)<nThreshold)
    {
     end++;
     GrowX[end]=xx;
     GrowY[end]=yy;
     *lpDst=0;
    }
   }
   start++;
  }
  //复制目的图像到源图像中
  memcpy(srcImage,dstImage,imageHeight*imageWidth);
  //删除目的图像
  delete[] dstImage;
  //删除堆栈
  delete[] GrowY;
  delete[] GrowX;
 }
 else if(nBitCount==24)
 {
  AfxMessageBox("暂时不能处理24位位图");
  return false;
 }
 else
 {
  AfxMessageBox("暂时只能处理8位位图");
  return false;
 }
 return true;
}

程序运行时截图

原始图片:

附加说明

由于我在自适应阈值分割时采用的3×3的窗口,所以分割后可以看出明显的矩形。但是为了更平滑的话,你可以选择将图片划分为左上、左下、右上和右下等四个区域,然后取每个区域内所有像素灰度的均值作为该区域的门限,这样进行的阈值分割会有不错的效果。当然图片不一样,我们应当采用适当的方法。

轮廓提取

算法:如果源图像中有一点为黑,且它的8个相邻点都是黑色时,则将该点删除。当然了,处理前,如果图像不是二值的图像需要先将图像二值化,然后进行轮廓提取。

Visual C++6.0算法实现

/******************************************************************************
*函数功能:实现二值图像的轮廓提取
*函数声明:
*   BOOL OutLine(
*    BYTE* image,  -指向源图像的像素数据的指针
*    LONG imageWidth, -源图像的宽度(必须是4的倍数)
*    LONG imageHeight, -源图像的高度
*    int nBitCount  -源图像的位数(默认为8位)
*    )
******************************************************************************/
BOOL OutLine(BYTE* srcImage,LONG imageWidth,LONG imageHeight,int nBitCount=8)
{
 //新建一个和源图像大小相同的内存空间
 BYTE* dstImage=new BYTE[imageWidth*imageHeight];
 //初始化新的内存空间为全255
 memset(dstImage,(BYTE)255,imageWidth*imageHeight);
 if(nBitCount==8)
 {
  //如果不是二值图像,需先转换成二值图像,选定的变换阈值是120
  for(int i=0;i<imageHeight;i++)
  {
   for(int j=0;j<imageWidth;j++)
   {
    BYTE gray=*(srcImage+imageWidth*i+j);
    if(gray>=120)
    {
     *(srcImage+imageWidth*i+j)=255;
    }
    else
    {
     *(srcImage+imageWidth*i+j)=0;
    }
   }
  }

  //轮廓提取:如果源图像中有一点为黑,且它的8个相邻的点都是黑色时,则将此点删除
  //为防止越界,宽度和高度的上下左右各留出一行
  
  //存放处理像素的八个相邻的像素点
  unsigned char n,e,s,w,ne,se,nw,sw;
  for(i=1;i<imageHeight-1;i++)
  {
   for(int j=1;j<imageWidth-1;j++)
   {
    unsigned char* src=srcImage+imageWidth*i+j;
    unsigned char* dst=dstImage+imageWidth*i+j;
    if(*(src)==0)
    {
     *dst=0; //目标图像的相应像素也设置为黑点
     w=*(src-1); //左邻点
     if(w!=0) continue;
     e=*(src+1); //右邻点
     if(e!=0) continue;
     nw=*(src+imageWidth-1); //左上邻点
     if(nw!=0) continue;
     n=*(src+imageWidth); //上邻点
     if(n!=0) continue;
     ne=*(src+imageWidth+1); //右上邻点
     if(ne!=0) continue;
     sw=*(src-imageWidth-1); //左下邻点
     if(sw!=0) continue;
     s=*(src-imageWidth); //下邻点
     if(s!=0) continue;
     se=*(src-imageWidth+1); //右下邻点
     if(se!=0) continue;
     *dst=255;
    }
   }
  }

  //复制轮廓提取后的图像
  memcpy(srcImage,dstImage,imageWidth*imageHeight);
  //删除目的图像
  delete[] dstImage;
 }
 else if(nBitCount==24)
 {
  AfxMessageBox("对不起,这部分还没做完");
  return false;
 }
 else
 {
  AfxMessageBox("只能处理8或24位位图");
  return false;
 }

 return true;
}

程序运行时截图

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值