1.掩膜(mask)的定義
用選定的圖像,圖形或物體,對處理的圖像(全部或局部)進行遮擋,來控制圖像處理的區域或處理過程。用於覆蓋的特定圖像或物體稱為掩模或模板。光學圖像處理中,掩模可以足膠片,濾光片等。
掩模是由0和1組成的一個二進制圖像。當在某一功能中應用掩模時,1值區域被處理,被屏蔽的0值區域不被包括在計算中。通過指定的數據值,數據范圍,有限或無限值,感興趣區和注釋文件來定義圖像掩模,也可以應用上述選項的任意組合作為輸入來建立掩模。
掩膜是一種圖像濾鏡的模板,實用掩膜經常處理的是遙感圖像。當提取道路或者河流,或者房屋時,通過一個N * N的矩陣來對圖像進行像素過濾,然后將我們需要的地物或者標志突出顯示出來。這個矩陣就是一種掩膜。
在OpenCV的中,掩模操作是相對簡單的。大致的意思是,通過一個掩模矩陣,重新計算圖像中的每一個像素值。掩模矩陣控制了舊圖像當前位置以及周圍位置像素對新圖像當前位置像素值的影響力度。用數學術語講,即我們自定義一個權重表。
2.掩膜的用法
2.1 提取感興趣區:用預先制作的感興趣區掩膜與待處理圖像相乘,得到感興趣區圖像,感興趣區內圖像值保持不變,而區外圖像值都為0;
2.2 屏蔽作用:用掩膜對圖像上某些區域作屏蔽,使其不參加處理或不參加處理參數的計算,或僅對屏蔽區作處理或統計;
2.3 結構特征提取:用相似性變量或圖像匹配方法檢測和提取圖像中與掩膜相似的結構特征;
2.4 特殊形狀圖像的制作。
3.掩膜運算的一個小實例
以圖和掩膜的與運算為例:
原圖中的每個像素和掩膜中的每個對應像素進行與運算。比如1 & 1 = 1;1 & 0 = 0;
比如一個3 * 3的圖像與3 * 3的掩膜進行運算,得到的結果圖像就是:
掩膜操作實現圖像對比度調整
矩陣的掩膜操作十分簡單,根據掩膜來重新計算每個像素的像素值,掩膜(mask)也被稱為內核。
通過掩膜操作實現圖像對比度提高。I(i,j) = 5*I(i,j) - [I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]
Mat kern = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
紅色是中心像素,從上到下,從左到右對每個像素做同樣的處理操作,得到最終結果就是對比度提高之后的輸出圖像墊對象。
實例代碼:
#include <opencv2\opencv.hpp> #include <iostream> #include <math.h> using namespace std; using namespace cv; int main() { Mat src, dst; src = imread("E:\\VS2015Opencv\\vs2015\\project\\picture\\cat.jpg"); if (!src.data) { cout << "could not load image..." << endl; return -1; } namedWindow("source image", CV_WINDOW_AUTOSIZE); imshow("source image", src); //1).empty() 判斷文件讀取是否正確 //2).rows 獲取圖像行數(高度) //3).cols 獲取圖像列數(長度) //4).channels() 獲取圖像通道數 //5).depth() 獲取圖像位深度 //【1】記錄程序開始點timeStart double timeStart = (double)getTickCount();//計算時間語句 //進行矩陣的掩膜操作 int cols = (src.cols - 1)*src.channels();//837 //獲取圖像的列數,一定不要忘記圖像的通道數 int offsetx = src.channels();//圖像的通道數 3 int rows = src.rows;//220 dst = Mat::zeros(src.size(), src.type());//返回指定的大小和類型的數組 創建一個跟src一樣大小 類型的圖像矩陣 for (int row = 1; row < (rows - 1); row++) { //Mat.ptr<uchar>(int i=0) 獲取像素矩陣的指針,索引i表示第幾行,從0開始計行數。 //獲得當前行指針const uchar* current= myImage.ptr<uchar>(row ); //獲取當前像素點P(row, col)的像素值 p(row, col) =current[col] //Mat.ptr<uchar>(row):獲取第row行的圖像像素指針。圖像的行數從0開始計數 //獲取點P(row, col)的像素值:P(row.col) = Mat.ptr<uchar>(row)[col] const uchar *previous = src.ptr<uchar>(row - 1);//獲取上一行指針 const uchar *current = src.ptr<uchar>(row);//獲取當前行的指針 const uchar *next = src.ptr<uchar>(row + 1);//獲取下一行的指針 uchar *output = dst.ptr<uchar>(row);//目標對象像素 for (int col = offsetx; col < cols; col++) { //current[col-offsetx]是當前的像素點的左邊那個像素點的位置,因為一個像素點有三個通道 //current[col+offsetx]是當前的像素點的右邊那個像素點的位置,因為一個像素點有三個通道 //previous[col]表示當前像素點對應的上一行的那個像素點 //next[col]表示當前像素點對應的下一行的那個像素點 output[col] = 5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]); } } //OpenCV提高了函數filter2D來實現掩膜操作: //Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//定義掩膜 //調用filter2D //filter2D(src, dst, src.depth(), kernel); double timeconsume = ((double)getTickCount() - timeStart) / getTickFrequency(); cout << "運行上面程序共耗時: " << timeconsume << endl; //輸出 掩膜操作后的圖像 namedWindow("contrast image", CV_WINDOW_AUTOSIZE); imshow("contrast image", dst); waitKey(0); return 0; }
我們可以看見掩膜操作后的圖像對比度明顯提高了,但是美中不足的是出現了一些不好的小斑點。這是因為這項像素點的值的范圍不在0~255之間了。
解決方法:
使用函數saturate_cast(像素值)
這個函數的作用就是確保RGB的值在0~255之間。
像素范圍處理saturate_cast <typename _Tp>()
- saturate_cast <UCHAR>( - 100),返回0
- saturate_cast <UCHAR>(288),返回255
- saturate_cast <UCHAR>(100),返回100
這個函數的功能是確保RGB值范圍在0〜255之間。
添加上:
#include <opencv2\opencv.hpp> #include <iostream> #include <math.h> using namespace std; using namespace cv; int main() { Mat src, dst; src = imread("E:\\VS2015Opencv\\vs2015\\project\\picture\\cat.jpg"); if (!src.data) { cout << "could not load image..." << endl; return -1; } namedWindow("source image", CV_WINDOW_AUTOSIZE); imshow("source image", src); //1).empty() 判斷文件讀取是否正確 //2).rows 獲取圖像行數(高度) //3).cols 獲取圖像列數(長度) //4).channels() 獲取圖像通道數 //5).depth() 獲取圖像位深度 //【1】記錄程序開始點timeStart double timeStart = (double)getTickCount();//計算時間語句 //進行矩陣的掩膜操作 int cols = (src.cols - 1)*src.channels();//837 //獲取圖像的列數,一定不要忘記圖像的通道數 int offsetx = src.channels();//圖像的通道數 3 int rows = src.rows;//220 dst = Mat::zeros(src.size(), src.type());//返回指定的大小和類型的數組 創建一個跟src一樣大小 類型的圖像矩陣 for (int row = 1; row < (rows - 1); row++) { //Mat.ptr<uchar>(int i=0) 獲取像素矩陣的指針,索引i表示第幾行,從0開始計行數。 //獲得當前行指針const uchar* current= myImage.ptr<uchar>(row ); //獲取當前像素點P(row, col)的像素值 p(row, col) =current[col] //Mat.ptr<uchar>(row):獲取第row行的圖像像素指針。圖像的行數從0開始計數 //獲取點P(row, col)的像素值:P(row.col) = Mat.ptr<uchar>(row)[col] const uchar *previous = src.ptr<uchar>(row - 1);//獲取上一行指針 const uchar *current = src.ptr<uchar>(row);//獲取當前行的指針 const uchar *next = src.ptr<uchar>(row + 1);//獲取下一行的指針 uchar *output = dst.ptr<uchar>(row);//目標對象像素 for (int col = offsetx; col < cols; col++) { //像素范圍處理saturate_cast<uchar> output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col])); //current[col-offsetx]是當前的像素點的左邊那個像素點的位置,因為一個像素點有三個通道 //current[col+offsetx]是當前的像素點的右邊那個像素點的位置,因為一個像素點有三個通道 //previous[col]表示當前像素點對應的上一行的那個像素點 //next[col]表示當前像素點對應的下一行的那個像素點 //output[col] = 5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]); } } double timeconsume = ((double)getTickCount() - timeStart) / getTickFrequency(); cout << "運行上面程序共耗時: " << timeconsume << endl; //輸出 掩膜操作后的圖像 namedWindow("contrast image", CV_WINDOW_AUTOSIZE); imshow("contrast image", dst); waitKey(0); return 0; }
函數調用filter2D功能
定義掩膜:Mat kernel = (Mat_(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D( src, dst, src.depth(), kernel ); 其中src與dst是Mat類型變量、src.depth表示位圖深度,有32、24、8等。
#include <opencv2\opencv.hpp> #include <iostream> #include <math.h> using namespace std; using namespace cv; int main() { Mat src, dst; src = imread("E:\\VS2015Opencv\\vs2015\\project\\picture\\cat.jpg"); if (!src.data) { cout << "could not load image..." << endl; return -1; } namedWindow("source image", CV_WINDOW_AUTOSIZE); imshow("source image", src); //1).empty() 判斷文件讀取是否正確 //2).rows 獲取圖像行數(高度) //3).cols 獲取圖像列數(長度) //4).channels() 獲取圖像通道數 //5).depth() 獲取圖像位深度 //【1】記錄程序開始點timeStart double timeStart = (double)getTickCount();//計算時間語句 //進行矩陣的掩膜操作 int cols = (src.cols - 1)*src.channels();//837 //獲取圖像的列數,一定不要忘記圖像的通道數 int offsetx = src.channels();//圖像的通道數 3 int rows = src.rows;//220 dst = Mat::zeros(src.size(), src.type());//返回指定的大小和類型的數組 創建一個跟src一樣大小 類型的圖像矩陣 for (int row = 1; row < (rows - 1); row++) { //Mat.ptr<uchar>(int i=0) 獲取像素矩陣的指針,索引i表示第幾行,從0開始計行數。 //獲得當前行指針const uchar* current= myImage.ptr<uchar>(row ); //獲取當前像素點P(row, col)的像素值 p(row, col) =current[col] //Mat.ptr<uchar>(row):獲取第row行的圖像像素指針。圖像的行數從0開始計數 //獲取點P(row, col)的像素值:P(row.col) = Mat.ptr<uchar>(row)[col] const uchar *previous = src.ptr<uchar>(row - 1);//獲取上一行指針 const uchar *current = src.ptr<uchar>(row);//獲取當前行的指針 const uchar *next = src.ptr<uchar>(row + 1);//獲取下一行的指針 uchar *output = dst.ptr<uchar>(row);//目標對象像素 for (int col = offsetx; col < cols; col++) { //像素范圍處理saturate_cast<uchar> output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col])); //current[col-offsetx]是當前的像素點的左邊那個像素點的位置,因為一個像素點有三個通道 //current[col+offsetx]是當前的像素點的右邊那個像素點的位置,因為一個像素點有三個通道 //previous[col]表示當前像素點對應的上一行的那個像素點 //next[col]表示當前像素點對應的下一行的那個像素點 //output[col] = 5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]); } } //OpenCV提高了函數filter2D來實現掩膜操作: Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//定義掩膜 //調用filter2D filter2D(src, dst, src.depth(), kernel); double timeconsume = ((double)getTickCount() - timeStart) / getTickFrequency(); cout << "運行上面程序共耗時: " << timeconsume << endl; //輸出 掩膜操作后的圖像 namedWindow("contrast image", CV_WINDOW_AUTOSIZE); imshow("contrast image", dst); waitKey(0); return 0; }
與前面沒有區別;
小結
1.圖像中,各種位運算,比如與、或、非運算與普通的位運算類似。
2.如果用一句話總結,掩膜就是兩幅圖像之間進行的各種位運算操作。
可以看看下面代碼:
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace std; using namespace cv; int main() { Mat image, mask; Rect r1(100, 100, 250, 300); Mat img1, img2, img3, img4; image = imread("E:\\VS2015Opencv\\vs2015\\project\\picture\\cat.jpg"); mask = Mat::zeros(image.size(), CV_8UC1); mask(r1).setTo(255); img1 = image(r1); image.copyTo(img2, mask); image.copyTo(img3); img3.setTo(0, mask); imshow("Image sequence", image); imshow("img1", img1); imshow("img2", img2); imshow("img3", img3); imshow("mask", mask); waitKey(); return 0; }
注意程序中的這兩句關於Mask的操作。
mask = Mat::zeros(image.size(), CV_8UC1);
mask(r1).setTo(255); //r1是已經設置好的興趣區域
解釋一下上面兩句的操作。
- 第一步建立與原圖一樣大小的mask圖像,並將所有像素初始化為0,因此全圖成了一張全黑色圖。
- 第二步將mask圖中的r1區域的所有像素值設置為255,也就是整個r1區域變成了白色。
這樣就能得到Mask圖像了。
image.copyTo(img2, mask);
這句是原始圖image拷貝到目的圖img2上。
其實拷貝的動作完整版本是這樣的:
原圖(image)與掩膜(mask)進行與運算后得到了結果圖(img2)。
說白了,mask就是位圖啊,來選擇哪個像素允許拷貝,哪個像素不允許拷貝。如果mask像素的值是非0的,我就拷貝它,否則不拷貝。
因為我們上面得到的mask中,感興趣的區域是白色的,表明感興趣區域的像素都是非0,而非感興趣區域都是黑色,表明那些區域的像素都是0。一旦原圖與mask圖進行與運算后,得到的結果圖只留下原始圖感興趣區域的圖像了。也正如下圖所示。
如果想要直接摳出目標區域,直接這樣寫就OK了:
img1 = image(r1);
image.copyTo(img3);
img3.setTo(0, mask);
上面兩句代碼所做的事情是首先將原始圖image拷貝一份給img3,然后img3將mask白色區域設置為0(黑色),好比如果mask中像素非0的,我就把我圖像對應的那個點的像素值設置為0,否則啥也不做。偽代碼是 if mask(i,j)>0 then img3(i,j)=0。
也就是說mask為1的位置設置為0,如下圖