形态学是什么呢,在生物学说的是研究动植物的形态、结构,延伸到图像莫非就是图像的轮廓、骨架、边界等等。形态学的基本操作是腐蚀与膨胀,简单的讲腐蚀就是消除掉某些小东西,膨胀就是扩大或者‘粗化’一些物体。但是,为了彻底搞清楚这两个东东,我们还是得从原理上搞清楚。
1、腐蚀与膨胀的定义
腐蚀:
B是结构元且有一个中心点,可以理解为移动的window或者卷积核,A是图像。公式表达的是:在图像A上平移结构元B,如果集合B完全包含于A则加入阵元的中心。
具体过程如下:
膨胀:
B是结构元且有一个中心点,可以理解为移动的window或者卷积核,A是图像。公式表达的是:在图像A上平移结构元B,如果集合B和A至少有一个元素重叠,则加入该阵元的像素。
具体过程如下:
从两个原理图:我们可以知道,腐蚀是删掉图像边界的某些像素,膨胀是给图像的边界添加一圈像素。通过基本的操作我们可以对图像进行消除噪声、边界提取、分割出独立的像素以及图像中响铃的像素
2、测试代码与结果
//图像的腐蚀操作
void imageErode(){
const char* name = "erode.tif";
IplImage* img = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
if (img == NULL){
printf("image load failed.\n");
return;
}
int iterationTimes = 1;//迭代 次数
IplImage* imgDst1 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
//第三个参数是卷积核默认是NULL,即3*3的模板,第四个参数是迭代次数
cvErode(img, imgDst1, NULL, iterationTimes);
//定义14*14的卷积核(结构元),中心位置在(6,6)核形状是矩形.由于预处理的部件垂直线条的宽度为11个像素,故卷积核的大小应该大于11
IplConvKernel* kernel = cvCreateStructuringElementEx(14, 14, 6, 6, CV_SHAPE_RECT);
IplImage* imgDst2 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
cvErode(img, imgDst2, kernel, iterationTimes);
cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Erode", CV_WINDOW_AUTOSIZE);
cvNamedWindow("mykernel", CV_WINDOW_AUTOSIZE);
cvShowImage("Source", img);
cvShowImage("Erode", imgDst1);
cvShowImage("mykernel", imgDst2);
cvWaitKey();
cvReleaseStructuringElement(&kernel);
cvReleaseImage(&img);
cvReleaseImage(&imgDst1);
cvReleaseImage(&imgDst2);
cvDestroyAllWindows();
}
结果: 第二张图是使用3*3的模板腐蚀,可以看到除了四条较宽的线没有被腐蚀掉,其它线均被腐蚀掉了。第三张图是使用的14*14的模板,由于加粗
线条大于11个像素,所有需要较大的模板,但是弊端是边上的线条都变细了很多。,,,,所以
,腐蚀缩小或者细化了图像中的物体,
并且
腐蚀通常用来消除图像中的斑点噪声。
//图像的膨胀操作
void imageDilate(){
const char* name = "dilate.tif";
IplImage* img = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
if (img == NULL){
printf("image load failed.\n");
return;
}
int iterationTimes = 1;//迭代 次数
IplImage* imgDst1 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
cvDilate(img, imgDst1, NULL, iterationTimes);
cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Erode", CV_WINDOW_AUTOSIZE);
cvShowImage("Source", img);
cvShowImage("Erode", imgDst1);
cvWaitKey();
cvReleaseImage(&img);
cvReleaseImage(&imgDst1);
cvDestroyAllWindows();
}
结果:如图所示,
膨胀‘粗化’了图像中的字符,使得断裂的某些地方连接了起来,膨胀最简单的应用就是桥接裂缝。
3、开操作与闭操作
开操作是先腐蚀在膨胀,能够平滑轮廓,断开较小的狭隘劲并且消除细小的物体。
闭操作是先膨胀再腐蚀,也能够起到平滑轮廓的作用,与开操作相反,它能够弥合较窄的间断,填补一些空隙。
OpenCV提供了cvMorphologyEx函数,可以进行多种图像操作,包括开、闭、形态梯度、礼貌、黑帽。
void cvMorphologyEx(const CvArr* src, CvArr* dst, CvArr* tmp, IplConvKernel* element, int operation, int iterations = 1);
src:输入图像 dst:输出图像 tmp:临时图像,某些操作会用到。需要使用tmp时,它应与原图像有同样的大小 element:结构元素
operation:形态操作的类型,有以下几种可用的类型:
-CV_MOP_OPEN
开运算不需要临时图像
-CV_MOP_CLOSE
闭运算不需要临时图像
-CV_MOP_GRADIENT 形态梯度需要临时图像
-CV_MOP_TOPHAT
“礼帽”src = dst情况下需要
-CV_MOP_BLACKHAT ”黑帽“src = dst情况下需要
测试代码:
//图像的开闭操作
void imageOpenClose(){
const char* name = "open.tif";
IplImage* img = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
if (img == NULL){
printf("image load failed.\n");
return;
}
int iterationTimes = 1;//迭代 次数
IplImage* temp = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
IplImage* imgDst1 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
//第三个参数是和原图像同样大小的,用于某些操作的中间转换 开闭操作可为空
//第四个参数是卷积核,默认是3*3
cvMorphologyEx(img, imgDst1, temp, NULL, CV_MOP_OPEN,iterationTimes);//
//对于开操作再进行膨胀,可以填补断裂、空隙
IplImage* imgDst1Better = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
cvDilate(imgDst1, imgDst1Better, NULL, iterationTimes);
IplImage* imgDst2 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
cvMorphologyEx(img, imgDst2, NULL, NULL, CV_MOP_CLOSE, iterationTimes);
cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
cvNamedWindow("open", CV_WINDOW_AUTOSIZE);
cvNamedWindow("openAndDilate", CV_WINDOW_AUTOSIZE);
cvNamedWindow("close", CV_WINDOW_AUTOSIZE);
cvShowImage("Source", img);
cvShowImage("open", imgDst1);
cvShowImage("openAndDilate", imgDst1Better);
cvShowImage("close", imgDst2);
cvWaitKey();
cvReleaseImage(&img);
cvReleaseImage(&imgDst1);
cvReleaseImage(&imgDst2);
cvReleaseImage(&imgDst1Better);
cvDestroyAllWindows();
}
对于闭操作,背景噪声完全没有被消除,即使指纹间的噪声被消除了,效果也很差。但是连接性比原图要好。最后一张图是先对图像开操作然后再膨胀,这样弥补了图二产生新的断裂的错误,并且纹路变粗了,虽然不知道这还少利还是弊,哈哈。
4、形态学梯度-边界提取
数字图像处理书中对于边界提取是通过原图像减去腐蚀的图像来提取边界。过程如下:
OpenCV是将膨胀后的图像和腐蚀后的图像做差,即结果= 膨胀-腐蚀 ,得到形态学梯度。个人觉得差别不是很大,先膨胀可能会增加一些边界线条的粗度,可以从结果图中进行观察分析。
测试代码:
void imageGradient(){
const char* name = "huang.jpg";
IplImage* img = cvLoadImage(name, CV_LOAD_IMAGE_GRAYSCALE);
if (img == NULL){
printf("image load failed.\n");
return;
}
int iterationTimes = 1;//迭代 次数
//形态学梯度:膨胀-腐蚀
IplImage* temp = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
IplImage* imgDst1 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
cvMorphologyEx(img, imgDst1, temp, NULL, CV_MOP_GRADIENT, iterationTimes);
//数字图像处理:边界提取 = 原图像-腐蚀
IplImage* imgDst2 = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
cvErode(img, imgDst2, NULL, iterationTimes);
cvAbsDiff(img, imgDst2, imgDst2);
cvNamedWindow("Source", CV_WINDOW_AUTOSIZE);
cvNamedWindow("Gradient", CV_WINDOW_AUTOSIZE);
cvNamedWindow("aa", CV_WINDOW_AUTOSIZE);
cvShowImage("Source", img);
cvShowImage("Gradient", imgDst1);
cvShowImage("aa", imgDst2);
cvWaitKey();
cvReleaseImage(&img);
cvReleaseImage(&imgDst1);
cvDestroyAllWindows();
}
结果图:图二是openCV的自带的函数处理,图三是利用原图像减去腐蚀的图像,可以看到图二的边界乣粗些,这是它先膨胀的原因。
终于写完了,虽然还不是很全,但是对于形态学的处理基本知道了,最近任务重,要学的东西太多,继续更新ing!!!