一、全體成員
經過了上一篇的膨脹、腐蝕以後,我們就可以用他們組合起來,形成了更多的形態效果,這樣就不會太多的改變原來圖像的大小,總結了一下,主要包含開運算、閉運算、頂帽、黑帽、形態學梯度、形態學角點、細化、填充這些方面。
1.開運算
對圖像進行先腐蝕後膨脹的操作就是圖像的開運算。
它的功能是有利於移走黑色前景下的白色小物體。
2.閉運算
對圖像進行先膨脹後腐蝕的操作就是圖像的閉運算。
它的功能是有利於移走黑色區域小洞。
3.形態學梯度
形態學梯度是一幅圖像腐蝕和膨脹的差值。
它有利於查找圖像的輪廓。
4.Top Hat
Top Hat是輸入圖像和它開運算的差值。
5.Black Hat
Black Hat是輸入圖像和它閉運算的差值。
6.形態學角點
dst=open-open
7細化
這個比較的複雜,講原理的話估計要還幾頁http://www.cnblogs.com/mikewolf2002/p/3321732.html,可以看看這篇文章說的很多,也很好
二、morphologyEx函數
其實,說了那麼多大多數都是和一個函數有關係那就是morphologyEx函數,這裏面給出了多種形態學類型的定義
<span style="font-size:18px;"><span style="font-size:18px;">void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,
InputArray kernel, Pointanchor, int iterations,
int borderType, constScalar& borderValue )
{
//拷貝Mat數據到臨時變量
Mat src = _src.getMat(), temp;
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
//一個大switch,根據不同的標識符取不同的操作
switch( op )
{
case MORPH_ERODE: //腐蝕
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_DILATE: //膨脹
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_OPEN: //開運算
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case CV_MOP_CLOSE: //閉運算
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case CV_MOP_GRADIENT: //形態學梯度
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
dst -= temp;
break;
case CV_MOP_TOPHAT: //頂帽
if( src.data != dst.data )
temp = dst;
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );
dst = src - temp;
break;
case CV_MOP_BLACKHAT: //黑帽
if( src.data != dst.data )
temp = dst;
dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);
erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);
dst = temp - src;
break;
default:
CV_Error( CV_StsBadArg, "unknown morphological operation" );
}
} </span></span>
三、開運算小例子
爲了好理解,簡單說明一個例子,其他的參考,改變參數就可以,下面是使用開運算作爲例子
<span style="font-size:18px;"><span style="font-size:18px;">//-----------------------------------------------------------------------------------------------
int main( )
{
Mat image = imread("1.jpg");
namedWindow("【原始圖】開運算");
namedWindow("【效果圖】開運算");
imshow("【原始圖】開運算", image);
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
morphologyEx(image,image, MORPH_OPEN, element);
imshow("【效果圖】開運算", image);
waitKey(0);
return 0;
} </span></span>
四、綜合成員
相信學習那麼長時間,上面的一看就明白了,那麼就可以進去綜合代碼
<span style="font-size:18px;"><span style="font-size:18px;">#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
Mat src, dst;
int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 2;
int const max_kernel_size = 21;
char* window_name = "Morphology Transformations Demo";
void Morphology_Operations( int, void* );
int main( int argc, char** argv )
{
src = imread( "lena.jpg" );
if( !src.data )
{ return -1; }
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name,
&morph_operator, max_operator, Morphology_Operations );
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations );
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
Morphology_Operations( 0, 0 );
waitKey(0);
return 0;
}
void Morphology_Operations( int, void* )
{
int operation = morph_operator + 2;
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
morphologyEx( src, dst, operation, element );
imshow( window_name, dst );
}</span></span>
再看來看看角點檢測,其實也是膨脹腐蝕的變形,只是沒有一個好名字
<span style="font-size:18px;">Mat result2;
dilate(image,result2,x);
erode(result2,result2,square);
imshow("result2",result2);
absdiff(result2,result,result);
imshow("result3",result);
threshold(result,result,40,255,THRESH_BINARY);</span>
細化,這個比較的複雜,需要好好的理解一下
圖像細化(Image Thinning),一般指二值圖像的骨架化(Image Skeletonization) 的一種操作運算。細化就是經過一層層的剝離,從原來的圖中去掉一些點,但仍要保持原來的形狀,直到得到圖像的骨架。骨架,可以理解爲圖象的中軸。
<span style="font-size:18px;">#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
#include <vector>
cv::Mat thinImage(const cv::Mat & src, const int maxIterations = -1)
{
assert(src.type() == CV_8UC1);
cv::Mat dst;
int width = src.cols;
int height = src.rows;
src.copyTo(dst);
int count = 0;
while (true)
{
count++;
if (maxIterations != -1 && count > maxIterations)
break;
std::vector<uchar *> mFlag; //用於標記需要刪除的點
//對點標記
for (int i = 0; i < height ;++i)
{
uchar * p = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
//如果滿足四個條件,進行標記
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
uchar p1 = p[j];
if (p1 != 1) continue;
uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
uchar p8 = (j == 0) ? 0 : *(p + j - 1);
uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
{
int ap = 0;
if (p2 == 0 && p3 == 1) ++ap;
if (p3 == 0 && p4 == 1) ++ap;
if (p4 == 0 && p5 == 1) ++ap;
if (p5 == 0 && p6 == 1) ++ap;
if (p6 == 0 && p7 == 1) ++ap;
if (p7 == 0 && p8 == 1) ++ap;
if (p8 == 0 && p9 == 1) ++ap;
if (p9 == 0 && p2 == 1) ++ap;
if (ap == 1 && p2 * p4 * p6 == 0 && p4 * p6 * p8 == 0)
{
mFlag.push_back(p+j);
}
}
}
}
//將標記的點刪除
for (std::vector<uchar *>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
{
**i = 0;
}
if (mFlag.empty())
{
break;
}
else
{
mFlag.clear();//將mFlag清空
}
for (int i = 0; i < height; ++i)
{
uchar * p = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
uchar p1 = p[j];
if (p1 != 1) continue;
uchar p4 = (j == width - 1) ? 0 : *(p + j + 1);
uchar p8 = (j == 0) ? 0 : *(p + j - 1);
uchar p2 = (i == 0) ? 0 : *(p - dst.step + j);
uchar p3 = (i == 0 || j == width - 1) ? 0 : *(p - dst.step + j + 1);
uchar p9 = (i == 0 || j == 0) ? 0 : *(p - dst.step + j - 1);
uchar p6 = (i == height - 1) ? 0 : *(p + dst.step + j);
uchar p5 = (i == height - 1 || j == width - 1) ? 0 : *(p + dst.step + j + 1);
uchar p7 = (i == height - 1 || j == 0) ? 0 : *(p + dst.step + j - 1);
if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6)
{
int ap = 0;
if (p2 == 0 && p3 == 1) ++ap;
if (p3 == 0 && p4 == 1) ++ap;
if (p4 == 0 && p5 == 1) ++ap;
if (p5 == 0 && p6 == 1) ++ap;
if (p6 == 0 && p7 == 1) ++ap;
if (p7 == 0 && p8 == 1) ++ap;
if (p8 == 0 && p9 == 1) ++ap;
if (p9 == 0 && p2 == 1) ++ap;
if (ap == 1 && p2 * p4 * p8 == 0 && p2 * p6 * p8 == 0)
{
//標記
mFlag.push_back(p+j);
}
}
}
}
for (std::vector<uchar *>::iterator i = mFlag.begin(); i != mFlag.end(); ++i)
{
**i = 0;
}
if (mFlag.empty())
{
break;
}
else
{
mFlag.clear();//將mFlag清空
}
}
return dst;
}
int main(int argc, char*argv[])
{
if (argc != 2)
{
std::cout << "參數個數錯誤!" << std::endl;
return -1;
}
cv::Mat src = cv::imread(argv[1], cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "讀取文件失敗!" << std::endl;
return -1;
}
cv::threshold(src, src, 128, 1, cv::THRESH_BINARY);
//圖像細化
cv::Mat dst = thinImage(src);
//顯示圖像
dst = dst * 255;
cv::namedWindow("src1", CV_WINDOW_AUTOSIZE);
cv::namedWindow("dst1", CV_WINDOW_AUTOSIZE);
cv::imshow("src1", src);
cv::imshow("dst1", dst);
cv::waitKey(0);
}</span>
填充可以參考這篇文章http://lib.csdn.net/article/opencv/28355
<span style="font-size:18px;">#include "cxcore.h"
#include "cv.h"
#include "highgui.h"
void FillInternalContours(IplImage *pBinary, double dAreaThre)
{
double dConArea;
CvSeq *pContour = NULL;
CvSeq *pConInner = NULL;
CvMemStorage *pStorage = NULL;
// 執行條件
if (pBinary)
{
// 查找所有輪廓
pStorage = cvCreateMemStorage(0);
cvFindContours(pBinary, pStorage, &pContour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
// 填充所有輪廓
cvDrawContours(pBinary, pContour, CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 2, CV_FILLED, 8, cvPoint(0, 0));
// 外輪廓循環
int wai = 0;
int nei = 0;
for (; pContour != NULL; pContour = pContour->h_next)
{
wai++;
// 內輪廓循環
for (pConInner = pContour->v_next; pConInner != NULL; pConInner = pConInner->h_next)
{
nei++;
// 內輪廓面積
dConArea = fabs(cvContourArea(pConInner, CV_WHOLE_SEQ));
printf("%f\n", dConArea);
if (dConArea <= dAreaThre)
{
cvDrawContours(pBinary, pConInner, CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 0, CV_FILLED, 8, cvPoint(0, 0));
}
}
}
printf("wai = %d, nei = %d", wai, nei);
cvReleaseMemStorage(&pStorage);
pStorage = NULL;
}
}
int Otsu(IplImage* src)
{
int height=src->height;
int width=src->width;
//histogram
float histogram[256] = {0};
for(int i=0; i < height; i++)
{
unsigned char* p=(unsigned char*)src->imageData + src->widthStep * i;
for(int j = 0; j < width; j++)
{
histogram[*p++]++;
}
}
//normalize histogram
int size = height * width;
for(int i = 0; i < 256; i++)
{
histogram[i] = histogram[i] / size;
}
//average pixel value
float avgValue=0;
for(int i=0; i < 256; i++)
{
avgValue += i * histogram[i]; //整幅圖像的平均灰度
}
int threshold;
float maxVariance=0;
float w = 0, u = 0;
for(int i = 0; i < 256; i++)
{
w += histogram[i]; //假設當前灰度i爲閾值, 0~i 灰度的像素(假設像素值在此範圍的像素叫做前景像素) 所佔整幅圖像的比例
u += i * histogram[i]; // 灰度i 之前的像素(0~i)的平均灰度值: 前景像素的平均灰度值
float t = avgValue * w - u;
float variance = t * t / (w * (1 - w) );
if(variance > maxVariance)
{
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
int main()
{
IplImage *img = cvLoadImage("lena.jpg", 0);
IplImage *bin = cvCreateImage(cvGetSize(img), 8, 1);
int thresh = Otsu(img);
cvThreshold(img, bin, thresh, 255, CV_THRESH_BINARY);
FillInternalContours(bin, 200);
cvNamedWindow("img");
cvShowImage("img", img);
cvNamedWindow("result");
cvShowImage("result", bin);
cvWaitKey(-1);
cvReleaseImage(&img);
cvReleaseImage(&bin);
return 0;
} </span>
五、matlab輔助
<span style="font-size:18px;">I=imread('d:\22.png');
SE=strel('rectangle',[3 3]);
I2=imopen(I,SE);
I3=imclose(I,SE);
I4=im2bw(I);
I5=bwmorph(I4,'thin',inf)
figure
subplot(131),imshow(I2),title('開運算')
subplot(132),imshow(I3),title('閉運算')
subplot(133),imshow(I5),title('細化')</span>
圖像識別算法交流 QQ羣:145076161,歡迎圖像識別與圖像算法,共同學習與交流