C++图形边缘检测与区域提取
我们将这个课题分为两部分,一是边缘检测,一是区域提取。
关于边缘检测:图像的边缘形成的原因是图像的灰度在某一区域的突然变化使得人眼才有了识别轮廓的功能。所以对于计算机识别边缘我们也可以用数学的方法定量的找出图像中灰度阶跃不连续或是线条不连续的地方,比如说一阶导数的极值点或二阶导数的零点的方法找到边缘。(图片摘自百度)。
[img]http://dl2.iteye.com/upload/attachment/0086/8785/e404332a-a703-3f11-a121-6dce248d25c6.png[/img]
当我们继续往下学时,越来越多的概念涌了出来,各种算子、幅度、差分、阈值、卷积运算。这对我这种数学渣渣的确是一种考验。其实我们没必要将边缘检索的数学推导都看懂。我们只要了解它的基本原理后,在运用它的结论就足以达到我们的目的。
我们总结了一下:什么Prewitt算子、Sobel算子,都是对灰度图片的一个变换,我们称这个变换为卷积模板变换,模板变换后,边缘就会被检索出来。首先让我们必须了解什么叫做卷积模板变换。这个博客说的很清楚,大家可以看看。
http://www.cnblogs.com/a-toad/archive/2008/10/24/1318921.html
所以我们根据这个运算规则,将它封装成一个方法,代码及注释如下
//模板操作,包括图像边缘的检测,图像的平滑都会用到这个函数
/*参数的意义
* img 指向图像的指针
* tempWidth tempHeight 模板的宽高
* tempX tempY 模板的中心的x y 坐标
* temp 指向模板数组的指针 tempCoef 模板的系数
* img2 为转化后的照片
*/
bool templateChange(IMAGE *img,int tempWidth,int tempHeight,int tempX,int tempY,float *temp,float tempCoef,IMAGE *img2){
long imgWidth = img->getwidth();
long imgHeight = img->getheight();
//得到原图的内容
DWORD *p = GetImageBuffer(img);
(*img2).Resize(imgWidth,imgHeight);
DWORD *p2 = GetImageBuffer(img2);
//用于暂存模板值
float result;
int endResult;
for (long i=tempY;i for (long j=tempX;j result=0;
//计算模板
for (int k=0;k for (int l=0;l //逻辑表达式较复杂(横坐标 i-tempy+k 纵坐标 j-tempx+1)
//得到灰度(RGB相同)
int z=GetRValue(p[imgWidth*(i+tempY-k)+j-tempX+l]);
result +=z*temp[k*tempWidth+l];
}
}
result *=tempCoef;
endResult =abs((int)result);
if (endResult>255){
endResult=255;
}
p2[i*imgWidth+j]=RGB(endResult,endResult,endResult);
}
}
return true;
}
了解了这个模板运算,我们反过头来再看各种算子
Sobel算子:
[img]http://dl2.iteye.com/upload/attachment/0087/2255/68db3872-c150-3b2b-9b55-33d41bfdb638.png[/img]
进行的横竖两个方向的模板运算后,遍历整个图片取相同像素点模板运算的最大值。
Prewitt算子:
[img]http://dl2.iteye.com/upload/attachment/0087/2257/c7a1e5b5-45bf-3189-af75-2dd5a8e991e5.png[/img]
同Sobel算子,进行的横竖两个方向的模板运算后,遍历整个图片取相同像素点模板运算的最大值。
给出了算法,写程序就很容易了。这里只展示prewitt算子的代码:
void GraphicCut(IMAGE *img){
IMAGE img1,img2;
float temp1[9]={1,0,-1,1,0,-1,1,0,-1};
float tempCoef1=1;
templateChange(img,3,3,1,1,temp1,tempCoef1,&img1);
float temp2[9]={-1,-1,-1,0,0,0,1,1,1};
float tempCoef2=1;
templateChange(img,3,3,1,1,temp2,tempCoef2,&img2);
DWORD *p = GetImageBuffer(img);
DWORD *p1 = GetImageBuffer(&img1);
DWORD *p2 = GetImageBuffer(&img2);
for (long i=0;igetheight();i++)
for (long j=0;jgetwidth();j++)
if (p1[i*img->getwidth()+j]getwidth()+j]){
p[i*img->getwidth()+j]=p2[i*img->getwidth()+j];
}else{
p[i*img->getwidth()+j]=p1[i*img->getwidth()+j];
}
}
效果图如下:
[img]http://dl2.iteye.com/upload/attachment/0087/2259/4f561acb-b049-3a46-97f8-bff7f7a7e6a2.jpg[/img]
[img]http://dl2.iteye.com/upload/attachment/0087/2261/ed21cff1-2ed1-313a-9789-a2abb5cdba62.png[/img]
啃完了边缘检索这块硬骨头后,我们又开始考虑区域要怎么提取。我们最初的想法是做一个类似于搜索的算法,遍历某个区域中的所有点,将这个区域中的像素点提取出来保存到另一幅图片中,就完成了区域提取。
我们最开始想到的是深度搜索,但对于一张500*500的图片来说,这种搜索对时间和空间的消耗都是特别大的,搞不好还会栈溢出。所以我们马上丢弃了这个想法。另一种搜索方法就是广度搜索了,理论上是可行的,所以我们决定试试这种算法。(其实网上有一些经过优化过的搜索算法,如扫描线算法)。
代码如下:
void graphicsFill(IMAGE *img1,IMAGE *img2,IMAGE *img3,int mouseX,int mouseY){
long imgWidth = img1->getwidth();
long imgHeight = img1->getheight();
DWORD *p1 = GetImageBuffer(img1);
DWORD *p2 = GetImageBuffer(img2);
DWORD *p3 = GetImageBuffer(img3);
list stk;
Point point;
point.x = mouseX;
point.y = mouseY;
stk.push_back(point);
int a[]={0,1,1,1,0,-1,-1,-1};
int b[]={1,1,0,-1,-1,-1,0,1};
while (!stk.empty()){
Point pt;
//获取链表头元素 ,并将其弹出
pt=stk.front(); stk.pop_front();
long y=pt.y;
long x=pt.x;
long z;
int x1,y1;
for (int i=0;i<8;i++){
x1=x+a[i]; y1=y+b[i];
z=abs(long(GetRValue(p1[y*imgWidth+x])-GetRValue(p1[y1*imgWidth+x1])));
if (p2[y1*imgWidth+x1]!=p3[y1*imgWidth+x1]&&z<4){
p3[y1*imgWidth+x1]=p2[y1*imgWidth+x1];
Point pt;
pt.x=x1; pt.y=y1;
stk.push_back(pt);
}
}
}
}
提取效果图片如下:
[img]http://dl2.iteye.com/upload/attachment/0087/2259/4f561acb-b049-3a46-97f8-bff7f7a7e6a2.jpg[/img]
[img]http://dl2.iteye.com/upload/attachment/0087/2263/4c58560b-7d0c-3575-846d-e4dbfb064e20.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0087/2267/d193023a-4474-3824-aad3-e9909e01adbb.png[/img]
整的来说,算法的效率还算高。但的确有更好的算法可以去解决这个问题,更好的算法就等读者自己去研究发现了。