国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

【OpenCV學習筆記】三、操作像素

2019-11-08 02:15:17
字體:
來源:轉載
供稿:網友

最近在系統地學習OpenCV,將學習的過程在此做一個記錄,主要以代碼+注釋的方式

記錄學習過程。

1.訪問像素值

  要訪問矩陣中的每個獨立元素,只需要指定它的行號和列號。返回的對應元素可以是單個數值,也可

以是多通道圖像的數值向量。給圖像加入椒鹽噪聲(salt-and-pepper noise),來說明如何直接訪問像素值。顧名思義,椒鹽噪聲是一個專門的噪聲類型,它隨機選擇一些像素,把它們的顏色替換成白色或黑色。

#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>void salt(cv::Mat image, int n){	int i, j;	for (int k = 0; k < n; k++) {		// rand()是隨機數生成器		//利用cv::Mat中公共成員變量cols和rows得到圖像的列數和行數		i = std::rand() % image.cols;		j = std::rand() % image.rows;		//使用type方法來區分灰度圖像和彩色圖像。		if (image.type() == CV_8UC1)     // 灰度圖像		{ 			//利用cv::Mat的at(int y,int x)方法可以訪問元素			//at方法被實現成一個模板方法,在調用時必須指定圖像元素的類型			image.at<uchar>(j, i) = 255;		}		else if (image.type() == CV_8UC3)  // 彩色圖像		{		/*彩色圖像的每個像素對應三個部分:紅色、綠色和藍色通道。因此包		含彩色圖像的cv::Mat類會返回一個向量,向量中包含三個8位的數值。 		OpenCV為這樣的短向量定義了一種類型,即cv::Vec3b。這個向量包含		三個無符號字符(unsigned character)類型的數據。因此,訪問彩色		像素中元素的方法如下:*/			image.at<cv::Vec3b>(j, i)[0] = 255;			image.at<cv::Vec3b>(j, i)[1] = 255;			image.at<cv::Vec3b>(j, i)[2] = 255;		}	}}/*修改圖像的函數在使用圖像作為參數時,都采用了值傳遞的方式。之所以這樣做,是因為它們在復制圖像時仍共享了同一塊圖像數據。因此在需要修改圖像內容時,圖像參數沒必要采用引用傳遞的方式*/int main(){	// 打開圖像	cv::Mat image = cv::imread("boldt.jpg");	// 調用函數以添加噪聲	salt(image, 3000);	// 顯示圖像	cv::namedWindow("Image");	cv::imshow("Image", image);	cv::waitKey(0);	return 0;}

運行結果:

2.用指針遍歷圖像

  以減少圖像中顏色的數量這個任務為例,來說明遍歷圖像的過程。

  彩色圖像由三個通道組成,每個通道對應三原色(紅、綠、藍)之一的強度。由于每個強度值

都是用一個8位的unsigned char表示,所以全部可能的顏色數目為256 × 256 × 256, 

大于1600萬個。理所當然,為了降低分析的復雜度,降低圖像中的顏色數目有時是有用的。

  基本的減色算法很簡單。假設N是減色因子,將圖像中每個像素的每個通道的值除以N

(使用整數除法,不保留余數)。然后將結果乘以N,得到N的倍數,并且剛好不超過原始像素值。

只需加上N/2,就得到相鄰的N倍數之間的中間值。對所有8位通道值重復這個過程,就會得到(256/N)× (256/N)×(256/N)種可能的顏色值。

#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>void colorReduce(cv::Mat image, int div = 64){	int nl = image.rows; // 行數	// 每行的元素數量	int nc = image.cols * image.channels();	for (int j = 0; j < nl; j++) {		// 取得行j的地址		/*為了簡化指針運算的計算過程,cv::Mat類提供ptr函數,可以		直接訪問圖像中任一行的地址。ptr函數是一個模板函數, 返回第j行的地址:*/		uchar* data = image.ptr<uchar>(j);		for (int i = 0; i < nc; i++) 		{			// 處理每個像素 ---------------------			data[i] = data[i] / div*div + div / 2;			// 像素處理結束 -----------			/*注意在處理語句中, 我們也可以采用另一種等價的做法, 即利用指針			運算從一列移到下一列。 因此可以使用下面的代碼:*/			//*data = *data / div*div + div2; data++;		} // 一行結束	}}int main(){	// 讀取圖像	cv::Mat image = cv::imread("boldt.jpg");	// 處理圖像	colorReduce(image, 64);	// 顯示圖像	cv::namedWindow("Image");	cv::imshow("Image", image);	cv::waitKey(0);	return 0;}運行結果:

3.用迭代器遍歷圖像  在面向對象編程時,我們通常用迭代器對數據集合進行循環遍歷。迭代器是一種類,

專門用于遍歷集合的每個元素,隱藏了遍歷過程的具體細節。標準模板庫(STL)對容器類型

都定義了對應的迭代器,OpenCV也提供了cv::Mat的迭代器,并且與C++ STL中的標準迭代器兼容。

  依然以減色程序為例。

#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>void colorReduce(cv::Mat &image, int div = 64) {	// 在初始位置獲得迭代器	/*要得到cv::Mat實例的迭代器,首先要創建一個cv::MatIterator_對象。 	跟cv::Mat_類似,這個下劃線表示它是一個模板子類。 因為圖像迭代器是用來	訪問圖像元素的,所以必須在編譯時就明確返回值的類型。 可以這樣定義迭代器:*/	cv::Mat_<cv::Vec3b>::iterator it;	/*然后就可以使用常規的迭代器方法begin和end對像素進行循環遍歷了。	不同之處在于它們仍然是模板方法。*/	it = image.begin<cv::Vec3b>(); 	// 獲得結束位置	cv::Mat_<cv::Vec3b>::iterator itend =		image.end<cv::Vec3b>();	// 循環遍歷所有像素	for (; it != itend; ++it) 	{		// 處理每個像素 ---------------------		/*注意這里處理的是一個彩色圖像, 因此迭代器返回cv::Vec3b實		例。 你可以用取值運算符[]訪問每個顏色通道的元素。*/		(*it)[0] = (*it)[0] / div*div + div / 2;		(*it)[1] = (*it)[1] / div*div + div / 2;		(*it)[2] = (*it)[2] / div*div + div / 2;		// 像素處理結束 ----------------	}}int main(){	// 讀取圖像	cv::Mat image = cv::imread("boldt.jpg");	// 處理圖像	colorReduce(image, 64);	// 顯示圖像	cv::namedWindow("Image");	cv::imshow("Image", image);	cv::waitKey(0);	return 0;}

不管掃描的是哪種類型的集合,使用迭代器時總是遵循同樣的模式。

首先你要使用合適的專用類創建迭代器對象,在本例中是cv::Mat_<cv::Vec3b>:: iterator,

然后可以用begin方法,在開始位置(本例中為圖像的左上角)初始化迭代器。對于cv::Mat實例,

可以使用image.begin<cv::Vec3b>()。 

還可以在迭代器上使用數學計算,例如若要從圖像的第二行開始,可以用

image.begin<cv::Vec3b>()+image.cols初始化cv::Mat迭代器。 

獲得集合結束位置的方法也類似,只是改用end方法。但是,用end方法得到的迭代器已經超出了

集合范圍,因此必須在結束位置停止迭代過程。結束的迭代器也能使用數學計算,例如,如果你想在最后一行前就結束迭代, 可使用image.end<cv::Vec3b>()-image.cols。初始化迭代器后,建立一個循環遍歷所有元素,直到與結束迭代器相等。 

典型的while循環就像這樣:

while (it!= itend) {// 處理每個像素 ---------------------// 像素處理結束 ---------------------++it;}

你可以用運算符++來移動到下一個元素,也可以指定更大的步幅。例如用it+=10, 

對每10個像素處理一次。最后,在循環內部使用取值運算符*來訪問當前元素,你可以用它來讀(例如element= *it;)或寫(例如*it= element;)。

運行結果(同2中指針遍歷的效果):

4.檢查代碼運行效率為了衡量函數或代碼段的運行時間,OpenCV有一個非常實用的函數,即cv::getTickCount(),

該函數返回從最近一次電腦開機到當前的時鐘周期數。因為我們希望得到以秒為單位的代碼運行時間,所以要使用另一個方法,即cv::getTickFrequency(),這個方法返回每秒的時鐘周期數。

為了獲得某個函數(或代碼段)的運行時間,通常需使用這樣的程序模板:

const int64 start = cv::getTickCount();colorReduce(image); // 調用函數// 經過的時間( 單位: 秒)double duration = (cv::getTickCount()-start)/cv::getTickFrequency();5.遍歷圖像和鄰域操作

在圖像處理中計算像素值時,經常需要用它的相鄰像素的值。

以對圖像進行銳化為例,在圖像處理領域有一個眾所周知的結論:如果從圖像中減去拉普拉斯算子部分,圖像的邊緣就會放大,因而圖像會變得更加尖銳。

用以下方法計算銳化的數值:

sharpened_pixel= 5*current-left-right-up-down;

這里不能使用就地處理,使用者必須提供一個輸出圖像。圖像掃描中使用了三個指針,一個表示當前行, 一個表示上面的行,另外一個表示下面的行。另外,在計算每一個像素時都需要訪問與它相鄰的像素,因此有些像素的值是無法計算的,包括第一行、最后一行、第一列、最后一列的像素。這個循環可以這樣寫:

#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>void sharpen(const cv::Mat &image, cv::Mat &result) {	// 判斷是否需要分配圖像數據。 如果需要, 就分配	result.create(image.size(), image.type());	int nchannels = image.channels(); // 獲得通道數	// 處理所有行( 除了第一行和最后一行)	for (int j = 1; j < image.rows - 1; j++) {		const uchar* PRevious =			image.ptr<const uchar>(j - 1); // 上一行		const uchar* current =			image.ptr<const uchar>(j); // 當前行		const uchar* next =			image.ptr<const uchar>(j + 1); // 下一行		uchar* output = result.ptr<uchar>(j); // 輸出行		for (int i = nchannels; i < (image.cols - 1)*nchannels; i++) {			*output++ = cv::saturate_cast<uchar>(				5 * current[i] - current[i - nchannels] -				current[i + nchannels] - previous[i] - next[i]);		}	} // 把未處理的像素設為0		result.row(0).setTo(cv::Scalar(0));	result.row(result.rows - 1).setTo(cv::Scalar(0));	result.col(0).setTo(cv::Scalar(0));	result.col(result.cols - 1).setTo(cv::Scalar(0));}int main(){	// 讀取圖像	cv::Mat image = cv::imread("boldt.jpg");	cv::Mat result;	// 處理圖像	sharpen(image, result);	// 顯示圖像	cv::namedWindow("Image");	cv::imshow("Image", result);	cv::waitKey(0);	return 0;}

5+.卷積操作

在對像素鄰域進行計算時, 通常用一個核心矩陣來表示。 這個核心矩陣展現了為得到預期結果, 如何將計算相關的像素組合起來。 針對本節使用的銳化濾波器, 核心矩陣可以是這樣的:

鑒于濾波是圖像處理中常見的操作,OpenCV專門為此定義了一個函數, 即cv::filter2D。 要使用這個函數, 只需要定義一個內核( 以矩陣的形式) , 調用函數并傳入圖像和內核, 即可返回濾波后的圖像。 因此, 使用這個函數可以很容易地重新定義銳化函數:

#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>void sharpen2D(const cv::Mat &image, cv::Mat &result) {	// 構造內核( 所有入口都初始化為0)	cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));	// 對內核賦值	kernel.at<float>(1, 1) = 5.0;	kernel.at<float>(0, 1) = -1.0;	kernel.at<float>(2, 1) = -1.0;	kernel.at<float>(1, 0) = -1.0;	kernel.at<float>(1, 2) = -1.0;	// 對圖像濾波	cv::filter2D(image, result, image.depth(), kernel);}int main(){	// 讀取圖像	cv::Mat image = cv::imread("boldt.jpg");	cv::Mat result;	// 處理圖像	sharpen2D(image, result);	// 顯示圖像	cv::namedWindow("Image");	cv::imshow("Image", result);	cv::waitKey(0);	return 0;}但是這段代碼報錯:“filter2D”: 不是“cv”的成員。不知為何。

6.實現簡單的圖像運算

圖像就是普通的矩陣,可以進行加、減、乘、除運算,我們使用算法運算符,將第二個圖像與輸入圖像進行組合。下面就是第二個圖像:這里我們把兩個圖像相加,用于創建特效圖或覆蓋圖像中的信息。 我們可以使用cv::add函數

來實現相加功能。現在我們想得到加權和,因此使用更精確的cv::addWeighted函數:

cv::addWeighted(image1,0.7,image2,0.9,0.,result);操作的結果是一個新圖像,如下圖所示:


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 博客| 革吉县| 宝清县| 揭西县| 大名县| 西和县| 海晏县| 额尔古纳市| 洛扎县| 九龙县| 舒城县| 津市市| 腾冲县| 华阴市| 花莲县| 青河县| 新昌县| 开封市| 海伦市| 高州市| 仁怀市| 阜新市| 涞源县| 凤山市| 林甸县| 社旗县| 石景山区| 英超| 疏附县| 呼伦贝尔市| 苗栗市| 临漳县| 霍邱县| 镇江市| 石嘴山市| 昭通市| 霞浦县| 北碚区| 台南县| 探索| 灌南县|