http://blog.csdn.net/u011021773/article/details/53065120
上一篇我講過什麼叫做卷積,而且提到了卷積與濾波其實不完全相同,因爲部分濾波是非線性的,比方說中值濾波,就是取每個局部地區的中位數,也就是中間大小的數。如果展開了類比,如果取局部最大值或者局部最小值會是什麼樣的呢?大值濾波?小值濾波?
哈哈,其實取大值濾波的官方學名叫做膨脹,小值濾波叫做腐蝕。一句話概括,膨脹就是取局部最大值的濾波,腐蝕就是取局部最小值的濾波。
話不多講,來上一段膨脹的代碼:
Mat MyDilate(Mat src, int KernelSize=3,int step=1)
{
Mat dst(src.rows,src.cols,src.type());
src.copyTo(dst);
//半個核的大小,用以計算濾波中心
int KernelHalfSize = (KernelSize - 1) / 2;
if (0 == KernelHalfSize)
{
return dst;
}
int channels = src.channels();
//膨脹是求最大值的濾波
for (int c = 0; c < channels; c++)
{
for (int col = 0; col < src.cols; col+=step)
{
for (int row = 0; row < src.rows; row+=step)
{
int max = 0;
for (int kc = col-KernelHalfSize; kc < col+KernelHalfSize; kc ++)
{
for (int kr = row- KernelHalfSize; kr < row+KernelHalfSize; kr ++)
{
if (src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c] > max)
{
//如果是彩色圖
if (3 == channels)
{
//這裏求三個數的中位數是爲了解決濾波邊界的問題,
//如果超出界限的就選邊界那一點的值,因爲是求最大值所以某個點多算幾次也沒有關係
max = src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c];
}
//如果是灰度圖
else if (1 == channels)
{
max = src.at<uchar>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1));
}
}
}
}
if (3 == channels)
{
dst.at<Vec3b>(row,col)[c] = max;
}
//如果是灰度圖
else if (1 == channels)
{
dst.at<uchar>(row,col) = max;
}
}
}
}
return dst;
}
左邊爲原圖,右邊爲膨脹後的圖。這幅圖我採用的核的大小爲7*7,步長爲5,可以看見原本該一起被刮掉的區域現在只有一個個白點被抹掉了,這是因爲步長太大很多區域沒有被處理,所以只有少部分黑色沒有了,變成了白點。
腐蝕就是跟膨脹相反的操作啦,把求最大值改成求最小值就OK了,代碼如下:
Mat MyErode(Mat src, int KernelSize = 3, int step = 1)
{
Mat dst(src.rows, src.cols, src.type());
src.copyTo(dst);
//半個核的大小,用以計算濾波中心
int KernelHalfSize = (KernelSize - 1) / 2;
if (0 == KernelHalfSize)
{
return dst;
}
int channels = src.channels();
//腐蝕是求最小值的濾波
for (int c = 0; c < channels; c++)
{
for (int col = 0; col < src.cols; col += step)
{
for (int row = 0; row < src.rows; row += step)
{
int min = 255;
for (int kc = col - KernelHalfSize; kc < col + KernelHalfSize; kc++)
{
for (int kr = row - KernelHalfSize; kr < row + KernelHalfSize; kr++)
{
if (src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c] < min)
{
//如果是彩色圖
if (3 == channels)
{
//這裏求三個數的中位數是爲了解決濾波邊界的問題,
//如果超出界限的就選邊界那一點的值,因爲是求最大值所以某個點多算幾次也沒有關係
min = src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c];
}
//如果是灰度圖
else if (1 == channels)
{
min = src.at<uchar>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1));
}
}
}
}
if (3 == channels)
{
dst.at<Vec3b>(row, col)[c] = min;
}
//如果是灰度圖
else if (1 == channels)
{
dst.at<uchar>(row, col) = min;
}
}
}
}
return dst;
}
效果如圖:
這裏我選用的依然是7 * 7步長爲1的模板,可以看到黑色的字明顯變粗,中間的小點也都變大,有一塊黑色區域的中間的白點幾乎消失了,這就是膨脹的效果,消除亮色區域,增大暗色區域。接着我們還是試一試調整一下步長,我把步長調整爲2,看看效果圖。
可以看見黑色大軍還是向外擴展了,但是由於有步長限制了,不能每個點都佔領到,所以出現了一個個的小黑點,只能得到點的勝利,不能得到面的勝利。同樣,核的大小決定了膨脹的力度,如果核特別大黑色的字將特別粗,可能粗成一個坨。 有了腐蝕和膨脹的基礎了,開閉操作就很好理解了,所謂的開操作就是先腐蝕後膨脹,閉操作就是先膨脹後腐蝕,效果如圖:
以上兩張圖分別是開操作和閉操作,核的大小爲7*7,步長爲1。
開閉操作的意義在哪裏呢?
通過觀察我們發現開操作的效果和腐蝕很像,都是消除了白色的雜質,這是因爲開操作是先腐蝕,後膨脹。通過一次腐蝕就已經將白色雜質消除了,但是此時字也變得很粗了,想要讓他還保持原來的面積,就需要再把它膨脹回來,而一些白點會被消除。比方說左側黑色圓圈中間的白點,還有去字中間的白色縫隙。閉操作是先膨脹後腐蝕,原本的黑字先變粗後變細了,因爲黑點已經被消除了,所以再膨脹的時候原來的黑點不會再出現了,但是大面積的黑線依然存在,並且經歷了一次變細又變粗的過程,這樣畫面上就只剩下黑色的字了,而旁邊墨水灑落留下的黑點全部都消失了。
爲什麼黑色圓圈和去字的中間都不是黑色,也不是白色,而是灰色呢?這點開始我也非常奇怪,趕緊檢查了程序,發現程序沒有問題,0和1的問題不存在有中間值。後來觀察圖片發現原來圖片是經過了有損壓縮的,所有的顏色邊緣都是漸變的,所以出現了灰色的邊緣,而經過開操作的放大就顯得非常明顯了。
最後再總結一下:
腐蝕就是求局部最小值的濾波操作,目的是去除亮色雜質。
膨脹就是求局部最大值的濾波操作,目的是去除暗色雜質。
開操作是先腐蝕後膨脹的過程,目的是去除亮色雜質並保持重要部分面積不變。
閉操作是先膨脹後腐蝕的過程,目的是去除暗色雜質並保持重要部分面積不變。
最後貼上我的全部代碼,以供參考。
#include "iostream"
#include "imgproc.hpp"
#include "highgui.h"
#include "opencv.hpp"
using namespace std;
using namespace cv;
//求三個數的中位數
int mid(int a, int b, int c)
{
int max = a;
int min = b;
if ((a <=b&&b<=c)|| (c <= b&&b <= a))
{
return b;
}
else if ((b <= a&&a <= c) || (c <= a&&a <= b))
{
return a;
}
else
{
return c;
}
}
Mat MyDilate(Mat src, int KernelSize=3,int step=1)
{
Mat dst(src.rows,src.cols,src.type());
src.copyTo(dst);
//半個核的大小,用以計算濾波中心
int KernelHalfSize = (KernelSize - 1) / 2;
if (0 == KernelHalfSize)
{
return dst;
}
int channels = src.channels();
//膨脹是求最大值的濾波
for (int c = 0; c < channels; c++)
{
for (int col = 0; col < src.cols; col+=step)
{
for (int row = 0; row < src.rows; row+=step)
{
int max = 0;
for (int kc = col-KernelHalfSize; kc < col+KernelHalfSize; kc ++)
{
for (int kr = row- KernelHalfSize; kr < row+KernelHalfSize; kr ++)
{
if (src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c] > max)
{
//如果是彩色圖
if (3 == channels)
{
//這裏求三個數的中位數是爲了解決濾波邊界的問題,
//如果超出界限的就選邊界那一點的值,因爲是求最大值所以某個點多算幾次也沒有關係
max = src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c];
}
//如果是灰度圖
else if (1 == channels)
{
max = src.at<uchar>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1));
}
}
}
}
if (3 == channels)
{
dst.at<Vec3b>(row,col)[c] = max;
}
//如果是灰度圖
else if (1 == channels)
{
dst.at<uchar>(row,col) = max;
}
}
}
}
return dst;
}
Mat MyErode(Mat src, int KernelSize = 3, int step = 1)
{
Mat dst(src.rows, src.cols, src.type());
src.copyTo(dst);
//半個核的大小,用以計算濾波中心
int KernelHalfSize = (KernelSize - 1) / 2;
if (0 == KernelHalfSize)
{
return dst;
}
int channels = src.channels();
//腐蝕是求最小值的濾波
for (int c = 0; c < channels; c++)
{
for (int col = 0; col < src.cols; col += step)
{
for (int row = 0; row < src.rows; row += step)
{
int min = 255;
for (int kc = col - KernelHalfSize; kc < col + KernelHalfSize; kc++)
{
for (int kr = row - KernelHalfSize; kr < row + KernelHalfSize; kr++)
{
if (src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c] < min)
{
//如果是彩色圖
if (3 == channels)
{
//這裏求三個數的中位數是爲了解決濾波邊界的問題,
//如果超出界限的就選邊界那一點的值,因爲是求最大值所以某個點多算幾次也沒有關係
min = src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c];
}
//如果是灰度圖
else if (1 == channels)
{
min = src.at<uchar>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1));
}
}
}
}
if (3 == channels)
{
dst.at<Vec3b>(row, col)[c] = min;
}
//如果是灰度圖
else if (1 == channels)
{
dst.at<uchar>(row, col) = min;
}
}
}
}
return dst;
}
//開操作先腐蝕後膨脹
Mat MyOpen(Mat img, int KernelSize = 3, int step = 1)
{
Mat ErodeImg = MyErode(img, KernelSize, step);
Mat DilateImg = MyDilate(ErodeImg, KernelSize, step);
return DilateImg;
}
//閉操作先膨脹後腐蝕
Mat MyClose(Mat img, int KernelSize = 3, int step = 1)
{
Mat DilateImg = MyDilate(img, KernelSize, step);
Mat ErodeImg = MyErode(DilateImg, KernelSize, step);
return ErodeImg;
}
void main()
{
Mat img=imread("..\\image\\original.png");
Mat ErodeImg = MyErode(img,7);
Mat DilateImg = MyDilate(img, 7);
Mat OpenImg = MyOpen(img, 7);
Mat CloseImg = MyClose(img, 7);
imshow("original", img);
imshow("ErodeImg", ErodeImg);
imshow("DilateImg", DilateImg);
imshow("OpenImg", OpenImg);
imshow("CloseImg", CloseImg);
waitKey(0);
imwrite("..\\image\\ErodeImg7.png", ErodeImg);
imwrite("..\\image\\DilateImg7.png", DilateImg);
imwrite("..\\image\\OpenImg.png", OpenImg);
imwrite("..\\image\\CloseImg.png", CloseImg);
}