學習體會:
- 當Mat為多通道時,如3通道,如果我們將其內容輸出到終端,則可以看出其列數為Mat::cols的n倍,當然n為Mat的通道數。雖是如此,但是Mat::cols的數值並沒有隨之改變。
- 當復制一副圖像時,利用函數cv::Mat::clone(),則將在內存中重新開辟一段新的內存存放復制的圖像(圖像數據也將全部復制),而如果利用cv::Mat::copyTo()復制圖像,則不會在內存中開辟一段新的內存塊,同時也不會復制圖像數據,復制前后的圖像的指針指向同一個內存塊。使用的時候需注意兩個函數的區別。
- 為了避免函數參數傳遞時出現復制情況,函數的形參多采用傳遞reference,如cv::Mat &image,傳遞輸入圖像的引用,不過這樣函數可能會對輸入圖像進行修改,並反映到輸出結果上;如果想避免修改輸入圖像,則函數形參可傳遞const reference,這樣輸入圖像不會被修改,同時可以創建一個輸出圖像Mat,將函數處理的結果保存到輸出圖像Mat中,例如:void colorReduce4(const cv::Mat &image, cv::Mat &result,int div = 64)。
- 采用迭代器iterator來遍歷圖像像素,可簡化過程,比較安全,不過效率較低;如果想避免修改輸入圖像實例cv::Mat,可采用const_iterator。
- 遍歷圖像時,不要采用.at()方式,這種效率最低。
- 進行圖像像素壓縮時,利用位操作的算法效率最高,其次是利用整數除法中向下取整,效率最低的是取模運算。
- 設計函數時,需要檢查計算效率來提高程序的性能,不過以犧牲程序的可讀性來提高代碼執行的效率並不是一個明智的選擇。
- 執行效率情況見程序運行結果。
參考資料:《OpenCV 2 Computer Vision Application Programming Cookbook》
1 /*************************************************************** 2 * 3 * 內容摘要:本例采用8種方法對圖像Mat的像素進行掃描,並對像素點的像 4 * 素進行壓縮,壓縮間隔為div=64,並比較掃描及壓縮的效率,效 5 * 率最高的是采用.ptr及減少循環次數來遍歷圖像,並采用位操 6 * 作來對圖像像素進行壓縮。 7 * 作 者:Jacky Liu 8 * 完成日期:2012.8.10 9 * 參考資料:《OpenCV 2 computer Vision Application Programming 10 * cookbook》 11 * 12 ***************************************************************/ 13 #include <opencv2/core/core.hpp> 14 #include <opencv2/imgproc/imgproc.hpp> 15 #include <opencv2/highgui/highgui.hpp> 16 #include <iostream> 17 18 19 //利用.ptr和數組下標進行圖像像素遍歷 20 void colorReduce0(cv::Mat &image, int div = 64) 21 { 22 int nl = image.rows; 23 int nc = image.cols * image.channels(); 24 25 //遍歷圖像的每個像素 26 for(int j=0; j<nl ;++j) 27 { 28 uchar *data = image.ptr<uchar>(j); 29 for(int i=0; i<nc; ++i) 30 { 31 data[i] = data[i]/div*div+div/2; //減少圖像中顏色總數的關鍵算法:if div = 64, then the total number of colors is 4x4x4;整數除法時,是向下取整。 32 } 33 } 34 } 35 36 37 //利用.ptr和 *++ 進行圖像像素遍歷 38 void colorReduce1(cv::Mat &image, int div = 64) 39 { 40 int nl = image.rows; 41 int nc = image.cols * image.channels(); 42 43 //遍歷圖像的每個像素 44 for(int j=0; j<nl ;++j) 45 { 46 uchar *data = image.ptr<uchar>(j); 47 for(int i=0; i<nc; ++i) 48 { 49 *data++ = *data/div*div + div/2; 50 } 51 } 52 } 53 54 55 //利用.ptr和數組下標進行圖像像素遍歷,取模運算用於減少圖像顏色總數 56 void colorReduce2(cv::Mat &image, int div = 64) 57 { 58 int nl = image.rows; 59 int nc = image.cols * image.channels(); 60 61 //遍歷圖像的每個像素 62 for(int j=0; j<nl ;++j) 63 { 64 uchar *data = image.ptr<uchar>(j); 65 for(int i=0; i<nc; ++i) 66 { 67 data[i] = data[i]-data[i]%div +div/2; //利用取模運算,速度變慢,因為要讀每個像素兩次 68 } 69 } 70 } 71 72 //利用.ptr和數組下標進行圖像像素遍歷,位操作運算用於減少圖像顏色總數 73 void colorReduce3(cv::Mat &image, int div = 64) 74 { 75 int nl = image.rows; 76 int nc = image.cols * image.channels(); 77 78 int n = static_cast<int>(log(static_cast<double>(div))/log(2.0)); //div=64, n=6 79 uchar mask = 0xFF<<n; //e.g. div=64, mask=0xC0 80 81 //遍歷圖像的每個像素 82 for(int j=0; j<nl ;++j) 83 { 84 uchar *data = image.ptr<uchar>(j); 85 for(int i=0; i<nc; ++i) 86 { 87 *data++ = *data&mask + div/2; 88 } 89 } 90 } 91 92 //形參傳入const conference,故輸入圖像不會被修改;利用.ptr和數組下標進行圖像像素遍歷 93 void colorReduce4(const cv::Mat &image, cv::Mat &result,int div = 64) 94 { 95 int nl = image.rows; 96 int nc = image.cols * image.channels(); 97 98 result.create(image.rows,image.cols,image.type()); 99 100 //遍歷圖像的每個像素 101 for(int j=0; j<nl ;++j) 102 { 103 const uchar *data_in = image.ptr<uchar>(j); 104 uchar *data_out = result.ptr<uchar>(j); 105 for(int i=0; i<nc; ++i) 106 { 107 data_out[i] = data_in[i]/div*div+div/2; //減少圖像中顏色總數的關鍵算法:if div = 64, then the total number of colors is 4x4x4;整數除法時,是向下取整。 108 } 109 } 110 } 111 112 //利用.ptr和數組下標進行圖像像素遍歷,並將nc放入for循環中(比較糟糕的做法) 113 void colorReduce5(cv::Mat &image, int div = 64) 114 { 115 int nl = image.rows; 116 117 //遍歷圖像的每個像素 118 for(int j=0; j<nl ;++j) 119 { 120 uchar *data = image.ptr<uchar>(j); 121 for(int i=0; i<image.cols * image.channels(); ++i) 122 { 123 data[i] = data[i]/div*div+div/2; //減少圖像中顏色總數的關鍵算法:if div = 64, then the total number of colors is 4x4x4;整數除法時,是向下取整。 124 } 125 } 126 } 127 128 //利用迭代器 cv::Mat iterator 進行圖像像素遍歷 129 void colorReduce6(cv::Mat &image, int div = 64) 130 { 131 cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>(); //由於利用圖像迭代器處理圖像像素,因此返回類型必須在編譯時知道 132 cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>(); 133 134 for(;it != itend; ++it) 135 { 136 (*it)[0] = (*it)[0]/div*div+div/2; //利用operator[]處理每個通道的像素 137 (*it)[1] = (*it)[1]/div*div+div/2; 138 (*it)[2] = (*it)[2]/div*div+div/2; 139 } 140 } 141 142 //利用.at<cv::Vec3b>(j,i)進行圖像像素遍歷 143 void colorReduce7(cv::Mat &image, int div = 64) 144 { 145 int nl = image.rows; 146 int nc = image.cols; 147 148 //遍歷圖像的每個像素 149 for(int j=0; j<nl ;++j) 150 { 151 for(int i=0; i<nc; ++i) 152 { 153 image.at<cv::Vec3b>(j,i)[0] = image.at<cv::Vec3b>(j,i)[0]/div*div + div/2; 154 image.at<cv::Vec3b>(j,i)[1] = image.at<cv::Vec3b>(j,i)[1]/div*div + div/2; 155 image.at<cv::Vec3b>(j,i)[2] = image.at<cv::Vec3b>(j,i)[2]/div*div + div/2; 156 } 157 } 158 } 159 160 //減少循環次數,進行圖像像素遍歷,調用函數較少,效率最高。 161 void colorReduce8(cv::Mat &image, int div = 64) 162 { 163 int nl = image.rows; 164 int nc = image.cols; 165 166 //判斷是否是連續圖像,即是否有像素填充 167 if(image.isContinuous()) 168 { 169 nc = nc*nl; 170 nl = 1; 171 } 172 173 int n = static_cast<int>(log(static_cast<double>(div))/log(2.0)); 174 uchar mask = 0xFF<<n; 175 176 //遍歷圖像的每個像素 177 for(int j=0; j<nl ;++j) 178 { 179 uchar *data = image.ptr<uchar>(j); 180 for(int i=0; i<nc; ++i) 181 { 182 *data++ = *data & mask +div/2; 183 *data++ = *data & mask +div/2; 184 *data++ = *data & mask +div/2; 185 } 186 } 187 } 188 189 const int NumTests = 9; //測試算法的數量 190 const int NumIteration = 20; //迭代次數 191 192 int main(int argc, char* argv[]) 193 { 194 int64 t[NumTests],tinit; 195 cv::Mat image1; 196 cv::Mat image2; 197 198 //數組初始化 199 int i=0; 200 while(i<NumTests) 201 { 202 t[i++] = 0; 203 } 204 205 int n = NumIteration; 206 207 //迭代n次,取平均數 208 for(int i=0; i<n; ++i) 209 { 210 image1 = cv::imread("../boldt.jpg"); 211 212 if(!image1.data) 213 { 214 std::cout<<"read image failue!"<<std::endl; 215 return -1; 216 } 217 218 // using .ptr and [] 219 tinit = cv::getTickCount(); 220 colorReduce0(image1); 221 t[0] += cv::getTickCount() - tinit; 222 223 // using .ptr and *++ 224 image1 = cv::imread("../boldt.jpg"); 225 tinit = cv::getTickCount(); 226 colorReduce1(image1); 227 t[1] += cv::getTickCount() - tinit; 228 229 // using .ptr and [] and modulo 230 image1 = cv::imread("../boldt.jpg"); 231 tinit = cv::getTickCount(); 232 colorReduce2(image1); 233 t[2] += cv::getTickCount() - tinit; 234 235 // using .ptr and *++ and bitwise 236 image1 = cv::imread("../boldt.jpg"); 237 tinit = cv::getTickCount(); 238 colorReduce3(image1); 239 t[3] += cv::getTickCount() - tinit; 240 241 //using input and output image 242 image1 = cv::imread("../boldt.jpg"); 243 tinit = cv::getTickCount(); 244 colorReduce4(image1,image2); 245 t[4] += cv::getTickCount() - tinit; 246 247 // using .ptr and [] with image.cols * image.channels() 248 image1 = cv::imread("../boldt.jpg"); 249 tinit = cv::getTickCount(); 250 colorReduce5(image1); 251 t[5] += cv::getTickCount() - tinit; 252 253 // using .ptr and *++ and iterator 254 image1 = cv::imread("../boldt.jpg"); 255 tinit = cv::getTickCount(); 256 colorReduce6(image1); 257 t[6] += cv::getTickCount() - tinit; 258 259 //using at 260 image1 = cv::imread("../boldt.jpg"); 261 tinit = cv::getTickCount(); 262 colorReduce7(image1); 263 t[7] += cv::getTickCount() - tinit; 264 265 //using .ptr and * ++ and bitwise (continuous+channels) 266 image1 = cv::imread("../boldt.jpg"); 267 tinit = cv::getTickCount(); 268 colorReduce8(image1); 269 t[8] += cv::getTickCount() - tinit; 270 } 271 272 cv::namedWindow("Result"); 273 cv::imshow("Result",image1); 274 cv::namedWindow("Result Image"); 275 cv::imshow("Result Image",image2); 276 277 std::cout<<std::endl<<"-------------------------------------------------------------------------"<<std::endl<<std::endl; 278 std::cout<<"using .ptr and [] = "<<1000*t[0]/cv::getTickFrequency()/n<<"ms"<<std::endl; 279 std::cout<<"using .ptr and *++ = "<<1000*t[1]/cv::getTickFrequency()/n<<"ms"<<std::endl; 280 std::cout<<"using .ptr and [] and modulo = "<<1000*t[2]/cv::getTickFrequency()/n<<"ms"<<std::endl; 281 std::cout<<"using .ptr and *++ and bitwise = "<<1000*t[3]/cv::getTickFrequency()/n<<"ms"<<std::endl; 282 std::cout<<"using input and output image = "<<1000*t[4]/cv::getTickFrequency()/n<<"ms"<<std::endl; 283 std::cout<<"using .ptr and [] with image.cols * image.channels() = "<<1000*t[5]/cv::getTickFrequency()/n<<"ms"<<std::endl; 284 std::cout<<"using .ptr and *++ and iterator = "<<1000*t[6]/cv::getTickFrequency()/n<<"ms"<<std::endl; 285 std::cout<<"using at = "<<1000*t[7]/cv::getTickFrequency()/n<<"ms"<<std::endl; 286 std::cout<<"using .ptr and * ++ and bitwise (continuous+channels) = "<<1000*t[8]/cv::getTickFrequency()/n<<"ms"<<std::endl; 287 std::cout<<std::endl<<"-------------------------------------------------------------------------"<<std::endl<<std::endl; 288 cv::waitKey(); 289 return 0; 290 }
運行結果圖: