<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## OpenCV 學習(像素操作 Manipuating the Pixels) OpenCV 雖然提供了許多類型的圖像處理函數,可以對圖像進行各種常見的處理,但是總會有些操作時沒有的,這時我們就需要自己來操縱像素,實現我們需要的功能。今天就來講講 OpenCV 進行像素級操作的幾種方法,并做個比較。 在 OpenCV 中,圖像用矩陣來表示,對應的數據類型為 cv::Mat 。 cv::Mat 功能很強大,矩陣的元素可以為字節、字、浮點數、數組等多種形式。對于灰度圖像,每個像素用一個 8 bit 字節來表示,對彩色圖像,每個像素是一個三個元素的數組,分別存儲 **BGR** 分量,這里大家沒看錯,就是 BGR 而不是 RGB,每個像素三個字節,第一個字節是藍色分量,別問我為啥設計成這樣,我也不知道。 ### 訪問單個像素 (at 函數) cv::Mat 類有個 at(int y, int x) 方法,可以訪問單個像素。但是我們知道cv::Mat 可以存儲各種類型的圖像。在調用這個函數時必須要指定返回的像素的類型,因為 at 函數是模板函數。 如果是灰度圖,我們知道像素是以無符號字符型變量的形式存儲的。那么要像下面這樣訪問。 ~~~ image.at<uchar>(j,i)= value; ~~~ 如果圖像是24位真彩色的,那么可以這樣: ~~~ image.at<cv::Vec3b>(j,i)[channel]= value; ~~~ 下面是個簡單的例子,打開一副彩色圖像,在上面隨機的添加一些噪聲。原始圖像如下: ![這里寫圖片描述](https://box.kancloud.cn/2016-04-26_571f1db4085f8.jpg "") 核心的代碼: ~~~ cv::Mat image = cv::imread("Q:\\test.jpg", CV_LOAD_IMAGE_COLOR); for(int k = 0; k < 1000; k++) { int i = rand() % image.cols; int j = rand() % image.rows; image.at<cv::Vec3b>(i, j)[0] = 255; image.at<cv::Vec3b>(i, j)[1] = 255; image.at<cv::Vec3b>(i, j)[2] = 255; } ~~~ 處理后的圖像如下: ![這里寫圖片描述](https://box.kancloud.cn/2016-04-26_571f1db436d46.jpg "") 像上面這樣每次用 at 函數時都指定類型很繁瑣。這時可以利用 cv::Mat 的派生類,cv::Mat_ 類,這個類是模板類。在建立這個類的實例時就要指定類型,之后就無需每次使用時再指定類型了。下面是個例子。 ~~~ cv::Mat_ <cv::Vec3b> ima = image; cv::namedWindow("Origin image", cv::WINDOW_NORMAL); cv::imshow("Origin image", image); for(int k = 0; k < 1000; k++) { int i = rand() % ima.cols; int j = rand() % ima.rows; ima(i, j)[0] = 255; ima(i, j)[1] = 255; ima(i, j)[2] = 255; } ~~~ 這個代碼處理后的效果是相同的。 上面的程序中有個 ~~~ ima = image; ~~~ 這里又涉及到 OpenCV 的一個特性,就是普通的矩陣拷貝操作都是所謂的淺拷貝。也就是說這樣操作后 ima 和 image 共享相同的圖像數據。 如果我們想要真正的復制圖像數據。這時可以用 clone() 方法。類似下面的代碼: ~~~ cv::Mat_ <cv::Vec3b> ima = image.clone(); ~~~ 這樣之后 ima 和 image 就完全獨立了。 ### 利用指針遍歷圖像像素 經常,我們的算法需要遍歷圖像的全部像素。這時用 at 函數就會很慢。更高效的訪問圖像像素的方式是利用指針。 簡單的說,我們通常是去獲得一行像素的頭指針。如果圖像是灰度的,則類似這樣操作。 ~~~ uchar* data = image.ptr<uchar>(j); ~~~ 如果圖像是 24 位彩色的,則可以這樣: ~~~ cv::Vec3b * data = image.ptr<cv::Vec3b> (j); ~~~ 實際上,即使是彩色圖像,也可以用一個 uchar 型指針去指向。只要我們自己去計算要訪問的像素相對行首的位置偏移是多少。比如下面的函數,可以處理灰度圖像,也能處理彩色圖像,作用是縮減圖像中使用到的顏色。 ~~~ void colorReduce(cv::Mat &image, int div=64) { int nl = image.rows; // number of lines // total number of elements per line int nc = image.cols * image.channels(); for (int j = 0; j < nl; j++) { // get the address of row j uchar* data= image.ptr<uchar>(j); for (int i = 0; i < nc; i++) { // process each pixel --------------------- data[i]= data[i] / div * div + div / 2; // end of pixel processing ---------------- } } // end of line } ~~~ 利用默認參數應用于我們的測試圖像后得到的結果如下: ![這里寫圖片描述](https://box.kancloud.cn/2016-04-26_571f1db4667b6.jpg "") 上面的代碼中有這么一行,是用來計算一行像素有多少個字節的。當然這個前提是每個channel 占用一個字節。 ~~~ int nc= image.cols * image.channels(); ~~~ 如果每個 channel 占用多個字節的話,上面的公式就是錯誤的了,這時我們可以這樣計算。 ~~~ int nc = image.cols * image.elemSize(); ~~~ image.elemSize() 得到的是每個像素的字節數。乘以一行有多少個像素,正好就是一行有多少個字節。 上面的例子中,我們用了兩重循環來遍歷圖像中的每一個像素。實際上,因為我們對每個像素進行的操作是相同的,我們根本不需要確定某個像素是哪一行的。因此上面的代碼還可以進一步優化,只用一個循環來完成。 但是這時我們要特別注意,有些圖像所占的內存空間是不連續的。在一行像素結束后,可能會空出一小塊內存。之所以會這樣是因為有些 CPU 的指令對數據有內存對其要求。這樣雖然浪費了一些空間,但是運算起來會更快速。 圖像所占內存是否是連續的可以利用 isContinuous() 來得到。如果是連續的則可以用一個循環將所有像素都處理完。下面是代碼,這個代碼兼顧了內存連續與不連續兩種情況,內存不連續時就退化為兩重循環: ~~~ void colorReduce2(cv::Mat &image, int div=64) { int nl = image.rows; // number of lines int nc = image.cols * image.channels(); if (image.isContinuous()) { // then no padded pixels nc = nc * nl; nl = 1; // it is now a 1D array } // this loop is executed only once // in case of continuous images for (int j = 0; j < nl; j++) { uchar* data = image.ptr<uchar>(j); for (int i = 0; i < nc; i++) { // process each pixel --------------------- data[i] = data[i] / div * div + div / 2; // end of pixel processing ---------------- } // end of line } } ~~~ 如果我們要獲得圖像數據的首地址,還可以這樣: ~~~ uchar *data = image.data; ~~~ 對于二維圖像數據來說,每行圖像所占據的字節數由成員變量 step 來存儲。因此: ~~~ data += image.step; ~~~ 使得 data 指向下一行圖像的內存首地址。 當然,上面這些操作都是比較低級的指針操作,不建議使用。 ### 利用 iterators 來遍歷圖像數據 C++ 的標準模板庫(STL)中大量的使用到了 iterator。OpenCV 也模仿 STL 顯示了自己的一套 iterator。 OpenCV 中設計了 cv::MatIterator_ 類,這個類與 cv::Mat_ 類似,也是模板類。將這個類實例化時需要指定具體的類型。比如下面的代碼: ~~~ cv::MatIterator_<cv::Vec3b> it; ~~~ 另一種使用方法如下: ~~~ cv::Mat_<cv::Vec3b>::iterator it; ~~~ 如果我們只是用 iterator 來讀取像素值而不改變它,則可以用常量型 iterator. ~~~ cv::MatConstIterator_<cv::Vec3b> it; cv::Mat_<cv::Vec3b>::const_iterator it; ~~~ 上面的例子用 iterator 重寫后代碼如下: ~~~ void colorReduce3(cv::Mat &image, int div=64) { // obtain iterator at initial position cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>(); // obtain end position cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>(); // loop over all pixels for ( ; it!= itend; ++it) { // process each pixel --------------------- (*it)[0] = (*it)[0] / div * div + div / 2; (*it)[1] = (*it)[1] / div * div + div / 2; (*it)[2] = (*it)[2] / div * div + div / 2; } } ~~~ 這種方式有利也有弊,最大的缺點是這個代碼只能處理 24 位真彩色圖像。優點是無需關注內存是否連續的問題了。相對來說,利用 iterator 的代碼的運算速度比直接指針操作還是要稍微的慢一點。 ### 各種方法的速度比較 上面介紹了幾種訪問圖像像素的方法,在不考慮效率的前提下,這些方法都很好,可以實現同樣的功能。但是在計算機視覺應用場景中,計算效率(運行速度)經常是我們必須要考慮的關鍵因素。 因此這里專門用一個小節來比較各種方法的運行速度。 為了完整性,下面也給出了一個用 at函數訪問像素的 colorReduce 函數。 ~~~ void colorReduce0(cv::Mat &image, int div=64) { int nl = image.rows; // number of lines int nc = image.cols; for (int j=0; j<nl; j++) { for (int i=0; i<nc; i++) { // process each pixel --------------------- image.at<cv::Vec3b>(j,i)[0]= image.at<cv::Vec3b>(j,i)[0]/div*div + div/2; image.at<cv::Vec3b>(j,i)[1]= image.at<cv::Vec3b>(j,i)[1]/div*div + div/2; image.at<cv::Vec3b>(j,i)[2]= image.at<cv::Vec3b>(j,i)[2]/div*div + div/2; // end of pixel processing ---------------- } // end of line } } ~~~ 回顧一下我們實現的幾種方法。 - colorReduce0(): 使用 at 函數訪問像素 - colorReduce1(): 兩重循環,用指針訪問像素 - colorReduce2(): 當圖像內存連續時用一重循環訪問所有像素 - colorReduce3(): 用 iterator 訪問像素 利用 cv::getTickCount() 來計算各個函數的運行時間。 - colorReduce0(): 240579 - colorReduce1(): 22363 - colorReduce2(): 21202 - colorReduce3(): 77573 結果一目了然,colorReduce0 運行的最慢,其次是 colorReduce3。 colorReduce1 和 colorReduce2 相差的不多。因此,我們寫程序時,應盡可能的采用 colorReduce2 或 colorReduce1 這樣的用法。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看