今天我們來看一下如何訪問圖像的像素,以及如何改變圖像的亮度與對比度。
在之前我們先來看一下圖像矩陣數據的排列方式。我們以一個簡單的矩陣來說明:
對單通道圖像排列如下:
對於雙通道圖像排列如下:
那么對於三通道的RGB圖像則為:
知道了排列方式之后我們來討論一下訪問圖像像素常用的三種方式:
1.使用指針訪問;
2.使用迭代器訪問;
3.使用動態地址訪問;
為了比較一下三種方式的效率,我們介紹兩個函數來統計一下每種方式所需的時間。
int64 getTickCount()函數:返回CPU自某個時間(如開啟電腦)以來走過的時鍾周期數。
double getTickFrequency()函數:返回每秒鍾CPU走過的時鍾周期數。
然后我們來看第一種方式。
1.使用指針訪問圖像像素:我們將輸入圖像img_src的每一個像素值加上50后賦值給輸出圖像img_dst。
1 int main() 2 { 3 int c; 4 Mat img_src = imread("1.jpg"); 5 Mat img_dst; 6 7 namedWindow("原圖"); 8 namedWindow("處理圖"); 9 10 int channels = img_src.channels();//獲取圖像通道數 11 img_dst = img_src.clone(); 12 13 double time1 = static_cast<double>(getTickCount());//獲取開始處理前時間 14 15 for (int i = 0; i < img_src.rows; i++)//訪問圖像行數據 16 { 17 uchar* p_data1 = img_src.ptr(i);//獲取圖像行首地址 18 uchar* p_data2 = img_dst.ptr(i);//獲取圖像行首地址 19 for (int k = 0; k < img_src.cols*channels; k++)//獲取圖像列(含通道) 20 { 21 p_data2[k] = saturate_cast<uchar>(p_data1[k] + 50);//圖像處理 22 23 //*p_data2++ = saturate_cast<uchar>((*p_data1++) + 100);//與上一行圖像處理的等效方式 24 //*(p_data2 + k) = saturate_cast<uchar>(*(p_data1 + k) + 50);//同上 25 } 26 } 27 28 double time2 = static_cast<double>(getTickCount());//獲取結束處理時間 29 30 time1 = (time2 - time1) / getTickFrequency();//計算處理所用時間 31 cout << "指針訪問像素時間(S):" << time1 << endl; 32 33 while (1) 34 { 35 imshow("原圖", img_src);//顯示圖像 36 imshow("處理圖", img_dst);//顯示圖像 37 c = waitKey(0); 38 if (c == 27 || char(c) == 'q' || char(c) == 'Q')//按下Q鍵或者ESC鍵退出程序 39 break; 40 } 41 return 0; 42 }
2.使用迭代器方式:
1 int main() 2 { 3 int c; 4 Mat img_src = imread("1.jpg"); 5 Mat img_dst; 6 7 namedWindow("原圖"); 8 namedWindow("處理圖"); 9 10 int channels = img_src.channels();//獲取圖像通道數 11 img_dst = img_src.clone(); 12 13 double time1 = static_cast<double>(getTickCount());//獲取開始處理前時間 14 15 Mat_<Vec3b>::iterator it = img_src.begin<Vec3b>();//獲取原圖開始地址 16 Mat_<Vec3b>::iterator itend = img_src.end<Vec3b>();//獲取原圖結束地址 17 Mat_<Vec3b>::iterator it2 = img_dst.begin<Vec3b>();//獲取輸出圖開始地址 18 for (; it != itend; ++it, ++it2) 19 { 20 for (int i = 0; i < 3; i++) 21 { 22 (*it2)[i] = saturate_cast<uchar>((*it)[i] + 50);//圖像處理 23 } 24 } 25 26 double time2 = static_cast<double>(getTickCount());//獲取結束處理時間 27 28 time1 = (time2 - time1) / getTickFrequency();//計算處理所用時間 29 cout << "指針訪問像素時間(S):" << time1 << endl; 30 31 while (1) 32 { 33 imshow("原圖", img_src);//顯示圖像 34 imshow("處理圖", img_dst);//顯示圖像 35 c = waitKey(0); 36 if (c == 27 || char(c) == 'q' || char(c) == 'Q')//按下Q鍵或者ESC鍵退出程序 37 break; 38 } 39 return 0; 40 }
3.動態地址方式:
1 int main() 2 { 3 int c; 4 Mat img_src = imread("1.jpg"); 5 Mat img_dst; 6 7 namedWindow("原圖"); 8 namedWindow("處理圖"); 9 10 int channels = img_src.channels();//獲取圖像通道數 11 img_dst = img_src.clone(); 12 13 double time1 = static_cast<double>(getTickCount());//獲取開始處理前時間 14 15 for (int i = 0; i < img_src.rows; i++) 16 { 17 for (int k = 0; k < img_src.cols; k++) 18 { 19 for (int j = 0; j < channels; j++) 20 { 21 img_dst.at<Vec3b>(i, k)[j] = saturate_cast<uchar>(img_src.at<Vec3b>(i, k)[j] + 50); 22 } 23 } 24 } 25 26 double time2 = static_cast<double>(getTickCount());//獲取結束處理時間 27 28 time1 = (time2 - time1) / getTickFrequency();//計算處理所用時間 29 cout << "指針訪問像素時間(S):" << time1 << endl; 30 31 while (1) 32 { 33 imshow("原圖", img_src);//顯示圖像 34 imshow("處理圖", img_dst);//顯示圖像 35 c = waitKey(0); 36 if (c == 27 || char(c) == 'q' || char(c) == 'Q')//按下Q鍵或者ESC鍵退出程序 37 break; 38 } 39 return 0; 40 }
我們來看一下處理的結果吧:
實例
下面我們來看一個完整調用三種方式的例子,我們定義三個函數Mat image_bright1(Mat src);Mat image_bright2(Mat src);Mat image_bright3(Mat src);分別用來用三種方式處理圖片。
1 //************頭文件包含************* 2 #include "stdafx.h" 3 #include<iostream> 4 #include<opencv.hpp>//包含opencv的頭文件 5 //*********************************** 6 7 8 //************命名空間*************** 9 using namespace cv;//使用opencv命名空間 10 using namespace std; 11 //*********************************** 12 13 14 //************全局變量*************** 15 16 //*********************************** 17 18 19 //************全局函數*************** 20 Mat image_bright1(Mat src);//使用指針訪問像素 21 22 Mat image_bright2(Mat src);//使用迭代器訪問像素 23 24 Mat image_bright3(Mat src);//使用動態地址訪問像素 25 //*********************************** 26 27 28 //************主函數***************** 29 int main() 30 { 31 int c; 32 Mat img_src = imread("1.jpg"); 33 Mat img_dst1, img_dst2, img_dst3; 34 35 namedWindow("原圖",0); 36 namedWindow("指針訪問像素",0); 37 namedWindow("迭代器訪問像素",0); 38 namedWindow("動態地址訪問像素",0); 39 40 double time1 = static_cast<double>(getTickCount()); 41 img_dst1 = image_bright1(img_src);//使用指針訪問像素 42 43 double time2 = static_cast<double>(getTickCount()); 44 img_dst2 = image_bright2(img_src);//使用迭代器訪問像素 45 46 double time3 = static_cast<double>(getTickCount()); 47 img_dst3 = image_bright3(img_src);//使用動態地址訪問像素 48 49 double time4 = static_cast<double>(getTickCount()); 50 51 time1 = (time2 - time1) / getTickFrequency(); 52 time2 = (time3 - time2) / getTickFrequency(); 53 time3 = (time4 - time3) / getTickFrequency(); 54 55 cout << "指針訪問像素時間(S):"<<time1<<endl; 56 cout << "迭代器訪問像素時間(S):" << time2 << endl; 57 cout << "動態地址訪問像素時間(S):" << time3 << endl; 58 59 while (1) 60 { 61 imshow("原圖", img_src);//顯示圖像 62 imshow("指針訪問像素", img_dst1);//顯示圖像 63 imshow("迭代器訪問像素", img_dst2);//顯示圖像 64 imshow("動態地址訪問像素", img_dst3);//顯示圖像 65 66 c = waitKey(0); 67 if (c == 27 || char(c) == 'q' || char(c) == 'Q')//按下Q鍵或者ESC鍵退出程序 68 break; 69 } 70 71 return 0; 72 } 73 74 75 //使用指針訪問像素 76 Mat image_bright1(Mat src) 77 { 78 Mat dst; 79 int channels = src.channels(); 80 dst = src.clone(); 81 82 for (int i = 0; i < src.rows; i++) 83 { 84 uchar* p_data1 = src.ptr(i); 85 uchar* p_data2 = dst.ptr(i); 86 87 for (int k = 0; k < src.cols*channels; k++) 88 { 89 //*p_data2++ = saturate_cast<uchar>((*p_data1++) + 100); 90 //*(p_data2 + k) = saturate_cast<uchar>(*(p_data1 + k) + 50); 91 p_data2[k] = saturate_cast<uchar>(p_data1[k] + 50);//輸出圖像像素=原圖像像素+50 92 } 93 } 94 return dst; 95 } 96 97 98 //使用迭代器訪問像素 99 Mat image_bright2(Mat src) 100 { 101 Mat dst; 102 dst = src.clone(); 103 104 Mat_<Vec3b>::iterator it = src.begin<Vec3b>(); 105 Mat_<Vec3b>::iterator itend = src.end<Vec3b>(); 106 Mat_<Vec3b>::iterator it2 = dst.begin<Vec3b>(); 107 for (; it != itend; ++it, ++it2) 108 { 109 for (int i = 0; i < 3; i++) 110 { 111 (*it2)[i] = saturate_cast<uchar>(2*(*it)[i]);//輸出圖像像素=2*原圖像像素 112 } 113 } 114 return dst; 115 } 116 117 118 //使用動態地址訪問像素 119 Mat image_bright3(Mat src) 120 { 121 Mat dst; 122 int channels = src.channels(); 123 dst = src.clone(); 124 for (int i = 0; i < src.rows; i++) 125 { 126 for (int k = 0; k < src.cols; k++) 127 { 128 for (int j = 0; j < channels; j++) 129 { 130 dst.at<Vec3b>(i, k)[j] = saturate_cast<uchar>(2*src.at<Vec3b>(i, k)[j] + 50);//輸出圖像像素=2*原圖像像素+50 131 } 132 } 133 } 134 return dst; 135 }
結果:
從時間上我們可以看出來,使用指針的速度是最快的。
有些童鞋應該已經看出來了,在三種方法中我們將圖像像素的處理方法變了一下,得出的圖像也不一樣了。在三種方法中我們處理像素的計算方式分別為:
- 輸出圖像像素=原圖像像素+50;
- 輸出圖像像素=2*原圖像像素;
- 輸出圖像像素=2*原圖像像素+50;
其實這就是處理亮度與對比度的方法,從圖像上也能看出來。
總結一下:g(x)=k*f(x)+b;其中g(x)為輸出圖像,f(x)為輸入圖像;
- 調節k的值則可以改變圖像的對比度;
- 調節b的值則可以改變圖像的亮度;
下載
功能很簡單,代碼很少,建議自己寫一下或者在博文中復制一下,當然實在是懶的不要不要的土豪可以去下面的連接直接下載。
【opencv學習筆記七】訪問圖像中的像素與圖像亮度對比度調整