一。形态学图像处理的基本运算有4个:膨胀、腐蚀、开操作和闭操作。
结构元素大小的影响:
结构元素大的话,对于前景来说,被腐蚀掉的像素点会有好多,留下来的较少。 膨胀的像素点也会多。。。。 嗯嗯。大概这么个理解法。嘻嘻。
图像的腐蚀和膨胀:(二值图像,和灰度图像,调用的都是同一个函数,理解以二值图像来理解呗。)
腐蚀作用: 去噪音。 或者断开两个区域。
膨胀作用: 可以连通两个区域。
腐蚀膨胀的具体实现:(那个句号,代表了原点。)
腐蚀算法:
简单说,首先构造一个结构元素,结构元素的原点定位在待处理的目标像素上,当结构元素完全被包容在目标区域中,则原点所对应的目标像素点被保留;否则该点被腐蚀掉——成为新的背景点。所有的保留点,构成腐蚀的结果图像。
膨胀算法:
简单说,首先构造一个结构元素B 和B的反射,结构元素的原点定位在背景像素上,当有目标点被覆盖(称之为击中)时,则该点被膨胀为目标点——原点对应的背景像素点被作为新的目标点保留下来,所有的保留点构成膨胀的结果图像。
上面的原理看了挺久的,终于找到一幅好理解的图。。不容易呀。
C++: void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1),int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& border-
Value=morphologyDefaultBorderValue() )
dilate同上。
结构元素默认3*3矩阵, 原点默认在中心处, 次数默认为1次。
咦,矩阵内容的在哪里,什么十字形什么的。
对前景腐蚀就是对背景膨胀,对前影膨胀就是对背景腐蚀。
开运算和闭运算:
开运算: 先腐蚀,后膨胀。
闭运算: 先膨胀,后腐蚀。
开闭运算的优点: 腐蚀或者膨胀算法非常好,但是改变了原目标物的大小。 开闭运算可以达到腐蚀,膨胀的效果,并且保持目标物大小基本保持不变。
C++: void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )
二 图像边缘探测和角探测。
1边缘探测原理:
原图作一次膨胀,原图作一次腐蚀,二者相减即是边缘图。 用的这个结构元素,被称为图像梯度。。不同的图像梯度,可以探测不同粗细的边。。(貌似是这样子,详见下一章)
可以用 cv::morphologyEx(image,mat_edge,cv::MORPH_GRADIENT,cv::Mat()) 函数直接实现。也可以手动膨胀腐蚀再相减。。
// 边缘检测, 一个膨胀 减去 一个腐蚀 相减就可以。
//探测边,只要用梯度算法就可以了。你敢信。。
cv::Mat mat_edge;
cv::morphologyEx(image,mat_edge,cv::MORPH_GRADIENT,cv::Mat());
/* 上面梯度算法具体实现,膨胀一次,腐蚀一次,两个相减就得出边。
cv::Mat tmp;
cv::dilate(image,mat_edge,cv::Mat());
cv::erode(image,tmp,cv::Mat());
cv::absdiff(mat_edge,tmp,mat_edge);
*/
cv::namedWindow("win_edge");
cv::imshow("win_edge",mat_edge);
cout<<"type = "<<mat_edge.type()<<endl;
cv::Mat mat_thre;
//二值化一下。变成黑白图,更加清楚
cv::threshold(mat_edge,mat_thre,40,255,cv::THRESH_BINARY);
//黑白对调一次 - -
cv::bitwise_not(mat_thre,mat_thre);
cv::namedWindow("win_edge_thre");
cv::imshow("win_edge_thre",mat_thre);
2. 角探测原理:
角探测没有直接调用的函数,检测角的算法是
1>.原图膨胀十字,用结果腐蚀菱形,
2>.原图膨胀x形,用结果腐蚀正方向 1- 2 所得即是。
以下内容转自: http://blog.csdn.net/sunny2038/article/details/9137759#t0
边缘检测:
形态学检测边缘的原理很简单,在膨胀时,图像中的物体会想周围“扩张”;腐蚀时,图像中的物体会“收缩”。比较这两幅图像,由于其变化的区域只发生在边缘。所以这时将两幅图像相减,得到的就是图像中物体的边缘。
角检测:
先用十字形的结构元素膨胀像素,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,导致只有在拐角处才会“收缩”,而直线边缘都未发生变化。
第二步是用X形膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。所以当两幅图像相减时,只保留了拐角处。
//拐角要用到4种图形的结构元素。
cv::Mat cross(5,5,CV_8U,cv::Scalar(0)), //十字
diamond(5,5,CV_8U,cv::Scalar(1)), //菱形
square(5,5,CV_8U,cv::Scalar(1)), //正方形
x(5,5,CV_8U,cv::Scalar(0)); // x 形。
// 十字初始化。 中间十字为1,其它为0
for(int i=0;i<5;++i)
{
cross.at<uchar>(2,i) = 1;
cross.at<uchar>(i,2) = 1;
}
// 菱形初始化 4个角,均有三个像素置成0,其它全为1
diamond.at<uchar>(0,0)= 0;
diamond.at<uchar>(0,1)= 0;
diamond.at<uchar>(1,0)= 0;
diamond.at<uchar>(4,4)= 0;
diamond.at<uchar>(3,4)= 0;
diamond.at<uchar>(4,3)= 0;
diamond.at<uchar>(4,0)= 0;
diamond.at<uchar>(4,1)= 0;
diamond.at<uchar>(3,0)= 0;
diamond.at<uchar>(0,4)= 0;
diamond.at<uchar>(0,3)= 0;
diamond.at<uchar>(1,4)= 0;
// 正方形全为1
// x形。 对角线为1,其它全为0
for(int i=0;i<5;++i)
{
x.at<uchar>(i,i)=1;
x.at<uchar>(i,4-i)=1;
}
//检测角的算法是:
// 1.原图膨胀十字,用结果腐蚀菱形,
// 2.原图膨胀x形,用结果腐蚀正方向 1-2所得即是。
cv::Mat tmp1;
cv::dilate(image,tmp1,cross);
cv::erode(tmp1,tmp1,diamond);
cv::Mat tmp2;
cv::dilate(image,tmp2,x);
cv::erode(tmp2,tmp2,square);
cv::Mat result_corner;
cv::absdiff(tmp1,tmp2,result_corner);
//二值化一下。变成黑白图,更加清楚
cv::threshold(result_corner,result_corner,40,255,cv::THRESH_BINARY);
cv::namedWindow("win_corner");
cv::imshow("win_corner",result_corner);
三: 图像分割算法。
1. 分水岭 图像分割算法。 (没有实现,感觉暂时不会用到,先标记在这里)
2. GrabCut 前景后景分割算法。以下两个博客介绍地十分详细。
http://blog.csdn.net/zouxy09/article/details/8534954
http://blog.csdn.net/lcy9819/article/details/6554864
使用 cv::grabCut 大概的步骤:
1> 划定区域。
2> 用掩码图形,确定哪些肯定是前景,哪些肯定是后景。
3> 调用函数。 得到结果(像素值有4种, 分别是 一定前景、一定后景、可能前景、可能后景)
4> 把一定前景和可能前景的位置标出来。
5> 根据4所得的位置,将前景显示出来。
cv::Mat image = cv::imread("/root/Desktop/photos/group.jpg");
if( !image.data )
{
cout<<"failed in read group.jpg..."<<endl;
return ;
}
cv::Rect rect(0,100,400,200);
cv::Mat result;
cv::Mat bgdModel,fgdModel;
//原图复制一份,画上框。
cv::Mat image_tmp = image.clone();
cv::rectangle(image_tmp,rect,cv::Scalar(0,0,255));
cv::namedWindow("win_image_tmp");
cv::imshow("win_image_tmp",image_tmp);
// 执行完算法后, result有四种值,0,1,2,3
cv::grabCut(image,result,rect,bgdModel,fgdModel,5,cv::GC_INIT_WITH_RECT);
cv::namedWindow("win_grabcut");
cv::imshow("win_grabcut",result);
//每个像素,比较,相等的话置为255 把所有可能是前景的像素点标志出来了。
cv::Mat mat_cmp;
cv::compare(result,cv::GC_PR_FGD,mat_cmp,cv::CMP_EQ);
cv::namedWindow("win_cmp");
cv::imshow("win_cmp",mat_cmp);
//把原图按照标志位 复制一份。
cv::Mat fgd;
image.copyTo(fgd,mat_cmp);
cv::namedWindow("win_fgd");
cv::imshow("win_fgd",fgd);