一、 怎樣遍歷圖像的每個像素點?html
二、 opencv圖像矩陣怎麼被存儲的?算法
三、 怎樣衡量咱們算法的性能?數組
四、 什麼是查表,爲何要使用它們?多線程
假設一張三通道RGB圖像,每一個像素通道有256種不一樣顏色取值,那麼一個像素點可能有256*256*256(1600多萬)種可能顏色取值,這對於實際計算來講,開銷是至關大的。而實際計算中,只須要少許的顏色值就能達到相同的效果。經常使用的一種方法是進行顏色空間縮減。用以下方法,咱們能夠將顏色空間取值減小10倍:dom
然而若是對每一個像素點,都應用一次公式減小顏色空間取值,開銷仍然很大,所以咱們引入一個新方法:查表。 ide
//定義查表 uchar table[256]; int divideWidth = 10; for (int i = 0;i < 256; ++i) { table[i] = (uchar)(divideWidth*(i/divideWidth)); }
divideWith能夠簡單理解爲取值減小的倍數,例如取值爲10,顏色取值由256種可能變成25種。單個像素也只有25*25*25(15625)種可能,較以前1600多萬種,計算量極大減小。而後將某個像素點某個通道的值,做爲查表的數組索引,能夠直接獲取到最後的顏色值,避免了數學運算的工做量。函數
opencv中,咱們須要常常衡量一個接口/算法的時間,經過使用Opencv兩個自帶的函數cv::getTickCount()和cv::getTickFrequency()能夠實現,前者記錄從系統啓動開始CPU計數次數,後者記錄CPU計數頻率,可用以下代碼實現時間衡量: 性能
double t = (double)getTickCount(); // do something ... t = ((double)getTickCount() - t)/getTickFrequency(); cout << "Times passed in seconds: " << t << endl;
再來回顧下以前的問題,圖像是怎麼在內存中被存儲的。假設咱們的圖像是一張n*m的灰度圖像,在內存中的存儲方式將會是這樣的:測試
若是圖像是一張RGB多通道圖像,實際在內存中存儲是這樣的:編碼
能夠注意到,通道順序是BGR而不是原有的RGB。另外因爲咱們的內存足夠大,咱們的矩陣能夠一行接一行連續被存儲,這樣能夠加快圖像掃描的速度,經過cv::Mat::isContinuous()函數確認圖像是否被連續存儲。
一談到性能,沒有什麼能比C 風格的[]數組訪問操做更高效了,所以能夠用以下高效的方式實現查表法減小顏色空間取值:
Mat& ScanImageAndReduceC(Mat& I,const uchar* const table) { //accept only char type matrices CV_Assert(I.depth() == CV_8U); int channels = I.channels(); int nRows = I.rows; int nCols = I.cols*channels; if(I.isContinuous()) { nCols *= nRows; nRows = 1; } int i,j; uchar *p; for ( i = 0; i < nRows; ++i) { p = I.ptr<uchar>(i); for(j = 0;j < nCols;++j) { p[j] = table[p[j]]; } } return I; }
此外,咱們還能夠經過opencv提供的遞歸方法實現圖像的遍歷:
Mat& ScanImageAndReduceIterator(Mat& I,const uchar* const table) { CV_Assert(I.depth() == CV_8U); const int channels = I.channels(); switch(channels) { case 1: { MatIterator_<uchar> it,end; for( it = I.begin<uchar>(),end = I.end<uchar>();it != end;++it) { *it = table[*it]; } break; } case 3: { MatIterator_<Vec3b> it,end; for(it = I.begin<Vec3b>(),end = I.end<Vec3b>();it != end;++it) { (*it)[0] = table[(*it)[0]]; (*it)[1] = table[(*it)[1]]; (*it)[2] = table[(*it)[2]]; } break; } } return I; }
同時,還可使用at方法實時計算圖像座標實現圖像的遍歷,新定義Mat_<Vec3b> _I是爲了編碼偷懶的方式,能夠直接使用()運算符而不是at函數:
Mat& ScanImageAndReduceRandomAccess(Mat& I,const uchar * const table) { CV_Assert(I.depth() == CV_8U); const int channels = I.channels(); switch(channels) { case 1: { for (int i = 0;i < I.rows;++i) for (int j = 0; j < I.cols; ++j) { I.at<uchar>(i,j) = table[I.at<uchar>(i,j)]; } break; } case 3: { Mat_<Vec3b> _I = I; for (int i = 0;i < I.rows; ++i) for (int j = 0;j < I.cols; ++j) { //_I.at<Vec3b>(i,j)[0] = table[_I.at<Vec3b>(i,j)[0]]; //_I.at<Vec3b>(i,j)[1] = table[_I.at<Vec3b>(i,j)[1]]; //_I.at<Vec3b>(i,j)[2] = table[_I.at<Vec3b>(i,j)[2]]; _I(i,j)[0] = table[_I(i,j)[0]]; _I(i,j)[1] = table[_I(i,j)[1]]; _I(i,j)[2] = table[_I(i,j)[2]]; } I = _I; break; } } return I; }
OpenCV庫也爲咱們提供一個快速查表的庫函數:
Mat lookUpTable(1, 256, CV_8U); uchar* p = lookUpTable.ptr(); for( int i = 0; i < 256; ++i) p[i] = table[i]; LUT(I, lookUpTable, J);
最後,咱們附上整個程序源碼,經過調用攝像頭,獲取圖像,而後對前100幀圖像利用查表法進行顏色空間縮減:
#include<opencv2/opencv.hpp> #include<cv.h> using namespace cv; using namespace std; Mat& ScanImageAndReduceC(Mat& I,const uchar* const table) { //accept only char type matrices CV_Assert(I.depth() == CV_8U); int channels = I.channels(); int nRows = I.rows; int nCols = I.cols*channels; if(I.isContinuous()) { nCols *= nRows; nRows = 1; } int i,j; uchar *p; for ( i = 0; i < nRows; ++i) { p = I.ptr<uchar>(i); for(j = 0;j < nCols;++j) { p[j] = table[p[j]]; } } return I; } Mat& ScanImageAndReduceIterator(Mat& I,const uchar* const table) { CV_Assert(I.depth() == CV_8U); const int channels = I.channels(); switch(channels) { case 1: { MatIterator_<uchar> it,end; for( it = I.begin<uchar>(),end = I.end<uchar>();it != end;++it) { *it = table[*it]; } break; } case 3: { MatIterator_<Vec3b> it,end; for(it = I.begin<Vec3b>(),end = I.end<Vec3b>();it != end;++it) { (*it)[0] = table[(*it)[0]]; (*it)[1] = table[(*it)[1]]; (*it)[2] = table[(*it)[2]]; } break; } } return I; } Mat& ScanImageAndReduceRandomAccess(Mat& I,const uchar * const table) { CV_Assert(I.depth() == CV_8U); const int channels = I.channels(); switch(channels) { case 1: { for (int i = 0;i < I.rows;++i) for (int j = 0; j < I.cols; ++j) { I.at<uchar>(i,j) = table[I.at<uchar>(i,j)]; } break; } case 3: { Mat_<Vec3b> _I = I; for (int i = 0;i < I.rows; ++i) for (int j = 0;j < I.cols; ++j) { //_I.at<Vec3b>(i,j)[0] = table[_I.at<Vec3b>(i,j)[0]]; //_I.at<Vec3b>(i,j)[1] = table[_I.at<Vec3b>(i,j)[1]]; //_I.at<Vec3b>(i,j)[2] = table[_I.at<Vec3b>(i,j)[2]]; _I(i,j)[0] = table[_I(i,j)[0]]; _I(i,j)[1] = table[_I(i,j)[1]]; _I(i,j)[2] = table[_I(i,j)[2]]; } I = _I; break; } } return I; } Mat& ScanImageAndReduceLut(Mat& I,Mat& J,const uchar * const table) { Mat lookUpTable(1,256,CV_8U); uchar* p = lookUpTable.ptr(); for ( int i = 0;i < 256; ++i) p[i] = table[i]; LUT(I,lookUpTable,J); return J; } int main( ) { Mat frame_input,frame_src,frame_reduce_c,frame_reduce_iterator,frame_reduce_random_access,frame_reduce_lut; VideoCapture capture(0); if(capture.isOpened()) { printf("打開攝像頭成功\n"); capture >> frame_input; printf("圖像分辨率爲:%d * %d,通道數爲%d\n",frame_input.rows,frame_input.cols,frame_input.channels()); } //定義查表 uchar table[256]; int divideWidth = 30; for (int i = 0;i < 256; ++i) { table[i] = (uchar)(divideWidth*(i/divideWidth)); } float time_cnts_c = 0,time_cnts_iterator = 0,time_cnts_random_access = 0,time_cnts_lut = 0; double tick = 0,number = 0; while(number < 100){ ++number; printf("讀取第%f幀圖像\n",number); capture >> frame_input; if(frame_input.empty()){ printf("--(!) No captured frame -- Break!"); } else{ frame_src = frame_input.clone(); frame_reduce_c = frame_input.clone(); frame_reduce_iterator = frame_input.clone(); frame_reduce_random_access = frame_input.clone(); tick = getTickCount(); ScanImageAndReduceC(frame_reduce_c,table); time_cnts_c += ((double)getTickCount()- tick)*1000 / getTickFrequency(); tick = getTickCount(); ScanImageAndReduceIterator(frame_reduce_iterator,table); time_cnts_iterator += ((double)getTickCount()- tick)*1000 / getTickFrequency(); tick = getTickCount(); ScanImageAndReduceRandomAccess(frame_reduce_random_access,table); time_cnts_random_access += ((double)getTickCount()- tick)*1000 / getTickFrequency(); tick = getTickCount(); ScanImageAndReduceLut(frame_src,frame_reduce_lut,table); time_cnts_lut += ((double)getTickCount()- tick)*1000 / getTickFrequency(); imshow("原始圖像", frame_src); imshow("ScanImageAndReduceC",frame_reduce_c); imshow("ScanImageAndReduceIterator",frame_reduce_iterator); imshow("ScanImageAndReduceRandomAccess",frame_reduce_random_access); imshow("ScanImageAndReduceLut",frame_reduce_lut); } waitKey(10); } printf("time_cnts_c:%f\n",time_cnts_c/100); printf("time_cnts_iterator:%f\n",time_cnts_iterator/100); printf("time_cnts_random_access:%f\n",time_cnts_random_access/100); printf("time_cnts_lut:%f\n",time_cnts_lut/100); waitKey(1000000); return 0; }
opencv教程給出的時間參考以下:
https://docs.opencv.org/master/db/da5/tutorial_how_to_scan_images.html
Method |
Time |
Efficient Way |
79.4717 milliseconds |
Iterator |
83.7201 milliseconds |
On-The-Fly RA |
93.7878 milliseconds |
LUT function |
32.5759 milliseconds |
實際在咱們環境上(480*640,3通道)測試的結果以下:
Method |
Time |
Efficient Way |
4.605026 milliseconds |
Iterator |
92.846123 milliseconds |
On-The-Fly RA |
240.321487 milliseconds |
LUT function |
3.741437 milliseconds |
實驗結果代表,使用opencv自帶的LUT函數,效率最高。這是由於OpenCV內建的多線程緣由。其次是c語言高效的[]數組訪問方式。