【opencv4.3.0教程】08之圖像掩膜(Mask)操作與執行時間

目錄

一、前言

二、溫故知新——像素基本操作

1、獲取像素指針

2、像素範圍處理

3、讀寫像素

三、圖像掩膜操作

1、怎麼理解掩膜Mask

2、掩膜實現

3、API-filter2D

四、執行時間


一、前言

圖像操作其實就是對像素進行操作,這些操作不僅僅是像前面那些基礎操作一樣簡單,只有獲取值啊,簡單賦值啊之類的。但是像素操作可不止有這麼簡單。

從今天這節內容開始,我們來一起聊一些更高級的像素操作,我們會講一些原理,並講解對應的API,通過實戰讓大家能夠對內容有更深刻的認識。

二、溫故知新——像素基本操作

前面我們講了幾個像素基本操作:

獲取像素指針:用於後續讀取某像素的信息及修改像素。通過像素指針來訪問像素。

控制像素範圍:將求得的像素值規範到0-255之間。

讀寫像素,利用像素指針獲取像素值及修改像素值。

1、獲取像素指針

獲取像素指針是可以獲得一個指向像素的指針,我們可以使用指針來訪問像素值,修改像素值。包括獲取灰度圖像像素指針和彩色圖像像素指針。

獲取方式如下:

	//灰度圖像
        src_gray.at<uchar>(y, x); //行在前,列在後,y表示行,x表示列
        src_gray.at<uchar>(Point(x, y));

        //彩色圖像
        Vec3b BGR = src.at<Vec3b>(row, col);
	int B = BGR.val[0];
	int G = BGR.val[1];
	int R = BGR.val[2];
	Scalar BGR1 = src.at<Vec3b>(Point(col, row));

 

2、像素範圍處理

像素範圍處理功能會將所有的輸入值都控制在0-255之間,小於0的返回0,大於255的返回255,其他不變。具體API如下:

saturate_cast<uchar>(number);

3、讀寫像素

讀寫像素是最基本的像素操作,一個是用於獲取像素值,一個是用於修改某個位置的像素值,上面獲取像素指針的代碼同時也讀取了像素,寫像素代碼如下:

//灰度圖像
image.at<uchar>(y, x) = 123;  
 
//彩色圖像
image.at<Vec3b>(y,x)[0]=0; // blue
image.at<Vec3b>(y,x)[1]=0; // green
image.at<Vec3b>(y,x)[2]=255; // red

 

三、圖像掩膜操作

1、怎麼理解掩膜Mask

首先我們先來看一下定義:

掩膜操作是指根據掩膜矩陣(也稱作核kernel)重新計算圖像中每個像素的值。

我們舉個例子來說明一下,然後我們再來解釋:

圖像與掩膜矩陣

比如上面的這個圖中,我們左邊是圖像,右邊是我們的掩膜矩陣,定義說,我們要根據掩膜矩陣重新計算圖像中每個像素的值。計算方式如下:

從左邊圖中找到黃色背景區域(從原圖中找到和掩膜矩陣大小相同的區域),對應位置相乘,然後求和:

5 * 0 + 5 * (-1) + 5 * 0 + 5 * (-1) + 5 * 5 + 5 * (-1) + 5 * 0 + 5 * (-1) + 5 * 0 = 5

掩膜運算

通過計算,我們就會得到右面的圖像,左邊的3×3的像素,經過計算,會得到右邊1×1的像素。然後,我們計算

然後,我們計算下一個像素點,也就是左邊的區域往後移動一個區域,一直到最後一個區域結束:

我們會發現,計算過後,圖像變小了(最外面有一圈沒有計算)。這個問題,我們先留在這裏,以後再說。

由於圖像所有都是5,五個中心減去四個相鄰,最後結果還是5,所以上面這個經過掩膜操作並沒有什麼太明顯的變化。

如果換個矩陣就不一樣啦:

通過上面的介紹,我想,我們可以對掩膜有個更加深刻的瞭解了,掩膜可以通過鄰近像素來修改自身像素值。

 

 

2、掩膜實現

瞭解了具體的原理,我們接下來通過代碼來自己實現一下掩膜操作吧!

核心代碼就是使用掩膜中心位置的值的五倍,減去上下左右四個位置的值:

	for (int i = 1; i < src_gray.rows-1; i++)
	{
		for (int j = 1; j < src_gray.cols-1; j++)
		{
			src_new.at<uchar>(i, j) =
				src_gray.at<uchar>(i, j) * 5 -   //中心
				src_gray.at<uchar>(i - 1, j) -   //上
				src_gray.at<uchar>(i + 1, j) -   //下
				src_gray.at<uchar>(i, j - 1) -   //左
				src_gray.at<uchar>(i, j + 1);    //右
		}
	}

全部代碼如下:

#include<iostream>
#include<opencv2\opencv.hpp>

using namespace std;
using namespace cv;


int main() {
	
	Mat src = imread("./image/sign.png"); 
	if (!src.data)
	{
		cout << "ERROR : could not load image.\n";
		return -1;
	}
	
	Mat src_gray;
	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	imshow("src_gray", src_gray);

	Mat src_new = Mat(Size(src_gray.cols,src_gray.rows),CV_8UC1);

	for (int i = 1; i < src_gray.rows-1; i++)
	{
		for (int j = 1; j < src_gray.cols-1; j++)
		{
			src_new.at<uchar>(i, j) =
				src_gray.at<uchar>(i, j) * 5 -   //中心
				src_gray.at<uchar>(i - 1, j) -   //上
				src_gray.at<uchar>(i + 1, j) -   //下
				src_gray.at<uchar>(i, j - 1) -   //左
				src_gray.at<uchar>(i, j + 1);    //右
		}
	}

	imshow("new src_gray", src_new);

	waitKey(0);
	return 0;
}

執行結果如下:

大家就能發現,圖像中文字邊界的位置的發生了明顯變化。

3、API-filter2D

我們自己實現了我們的掩膜操作,在opencv中,我們提供了專門的API來實現掩膜操作:

void filter2D( 
    InputArray src, 
    OutputArray dst, 
    int ddepth,                            
    InputArray kernel, 
    Point anchor = Point(-1,-1),                            
    double delta = 0, 
    int borderType = BORDER_DEFAULT 
);

函數參數含義如下:

(1)InputArray類型的src ,輸入圖像。

(2)OutputArray類型的dst ,輸出圖像,圖像的大小、通道數和輸入圖像相同。

(3)int類型的ddepth,目標圖像的所需深度。

(4)InputArray類型的kernel,卷積核(或者更確切地說是相關核)是一種單通道浮點矩陣;如果要將不同的核應用於不同的通道,請使用split將圖像分割成不同的顏色平面,並分別對其進行處理。。

(5)Point類型的anchor,表示錨點(即被平滑的那個點),注意他有默認值Point(-1,-1)。如果這個點座標是負值的話,就表示取核的中心爲錨點,所以默認值Point(-1,-1)表示這個錨點在覈的中心。。

(6)double類型的delta,在將篩選的像素存儲到dst中之前添加到這些像素的可選值。

(7)int類型的borderType,用於推斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT。

在一般使用中,我們只涉及到前面四個參數,

對於第五個參數,我們都是默認中心爲錨點,

對於第六個參數,一般來說我們很少會設置一個額外的值去調整像素值,所以也是默認爲0的。

對於第七個參數,因爲是剛開始,我們先不展開說明,因爲後續我們還會講到,在這裏,我們先採用默認,讓我們把重心放在前四個參數上面。

如下面這個例子:

filter2D( src, dst, src.depth(), kernel );

我們來使用一個完整的例子來說明一下:

 

執行結果如下:

我們能夠發現,我們的文字出現了白色的邊界。

如果我們再調整一下kernel,我們就可以得到很多類型的圖像:

大家也可以自己嘗試設置自己的kernel,一般來說有個原則就是儘量核所有數值加起來不要太大。一般都是讓求得的值爲1。

 

四、執行時間

我們留意到一個比較重要的內容,就是我們操作像素,是要遍歷所有像素的,這是很耗時間的操作。特別是瞭解深度學習的同學,當做深度學習圖像檢測要遍歷圖像做訓練時,運算量是極大的,所以我們需要獲取執行時間,來分析算法的優劣,進行算法效率比較。

在opencv中,我們提供了專門的API來獲取執行時間,全部功能代碼如下:

	double t = (double)getTickCount();
	// do something ...
	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "消耗的時間爲: " << t << endl;

這其中涉及到兩個API:

	getTickCount();
	getTickFrequency();

第一個是:函數返回特定事件(例如,當機器打開時)後的刻度數。它可用於初始化RNG或通過讀取函數調用前後的計時計數來測量函數執行時間。

第二個是:函數返回每秒的刻度數。

我們通過第一個執行得到運行前和運行後的刻度數,相減後得到運行過程中的刻度時長,然後除以每秒的刻度數,就能得到代碼以秒爲單位的運行時長了。

舉個例子:

	double t = (double)getTickCount();

	// do something ...
	Mat src_new;
	Mat kernel = (Mat_<char>(3, 3) << 1, -4, 1, -1, 7, -1, 1, -4, 1);
	filter2D(src, src_new, src.depth(), kernel);
	imshow("src_new: 1, -4, 1, -1, 7, -1, 1, -4, 1", src_new);

	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "消耗的時間爲: " << t << endl;

執行結果如下:

到這裏我們就說完啦,大家多做練習哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章