OpenCV中表示圖像的數據結構是cv::Mat,Mat對象本質上是一個由數值組成的矩陣。矩陣的每個元素表明一個像素,對於灰度圖像,像素是由8位無符號數來表示(0表明黑,255表明白);對於彩色圖像,每一個像素是一個三元向量,即由三個8位無符號數來表示三個顏色通道(Opencv中順次爲藍、綠、紅)。
咱們先來介紹下cv::Mat類的獲取像素的成員函數at(),其函數原型以下:ios
template<typename _Tp> _Tp& at(int i0, int i1); //因爲Mat能夠存聽任意數據類型的元素,因此該函數是用模板函數來實現的 //它自己不會進行任何數據類型轉換,在調用的過程當中須要指明像素的數據類型 //即要與矩陣中的數據類型相匹配。如: img.at<uchar>(i,j)=255 img.at<cv::Vec3b>(i,j)[0]=255
在OpenCV中通常有四種圖像遍歷的方式,第一種天然是最平凡的數組遍歷啦。爲方便起見,如下全部的示例都是In-place變換操做。c++
在這裏咱們經過操做像素的辦法來實現圖像的鏡像變換,即實現flip(img,img,1)的功能。代碼以下:數組
#include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; void Flip(Mat &img) { int rows=img.rows; int cols=img.cols; for(int i=0; i<rows; i++) { for(int j=0; j<cols/2; j++) { uchar t; if(img.channels()==1) { t=img.at<uchar>(i,j); img.at<uchar>(i,j)=img.at<uchar>(i,cols-1-i); img.at<uchar>(i,cols-1-i)=t; } else if(img.channels()==3) { for(int k=0; k<3; k++) { t=img.at<Vec3b>(i,j)[k]; img.at<Vec3b>(i,j)[k]=img.at<Vec3b>(i,cols-1-j)[k]; img.at<Vec3b>(i,cols-1-j)[k]=t; } } } } } int main() { Mat img1=imread("test.jpg"); //將任意一張名爲test.jpg的圖片放置於工程文件夾test中 imshow("First",img1); if(!img1.data) { cout<<"error! The image is not built!"<<endl; return -1; } Flip(img1); imshow("Second",img1); waitKey(); return 0; }
效果以下: ruby
OpenCV中cv::Mat類提供了成員函數ptr獲得圖像任意行的首地址。ptr函數是一個模板函數,其原型爲:數據結構
template<typename _Tp> _Tp* Mat::ptr(int i=0)
在這裏咱們經過操做像素的辦法來實現圖像的水平反轉,即實現flip(img,img,0)的功能。代碼以下:函數
#include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; void Flip(Mat &img) { int rows=img.rows; int cols=img.cols*img.channels(); for(int i=0; i<rows/2; i++) { uchar *p=img.ptr<uchar>(i); uchar *q=img.ptr<uchar>(rows-1-i); uchar t; for(int j=0; j<cols;j++) { t=*p; *p++=*q; *q++=t; } } } int main() { Mat img1=imread("test.jpg"); //將任意一張名爲test.jpg的圖片放置於工程文件夾test中 imshow("First",img1); if(!img1.data) { cout<<"error! The image is not built!"<<endl; return -1; } Flip(img1); imshow("Second",img1); waitKey(); return 0; }
效果以下: ui
指針遍歷圖像的過程當中,咱們可能會受以往遍歷矩陣的影響,獲得圖像首行地址後,想直接經過一個循環去遍歷rows*cols*img.channels()的內存,可是考慮到一些多媒體處理芯片在行的長度爲是4或8的倍數時,對圖像的處理會更加高效,因此OpenCV中對圖像的每行會填補一些額外像素(不顯示、不保存),將填補後的行的長度稱爲關鍵字,成員變量step表明以字節爲單位的圖像的有效寬度。所以,咱們只有在圖像的有效寬度等於圖像的真實寬度,即沒有填補時,進行一重循環遍歷。咱們能夠經過cv::Mat的成員函數isContinuous來判斷圖像是否對行進行了填充,返回值爲真,表示沒有對行進行填充,反之填充。此外,咱們能夠經過cv::Mat的成員變量data獲得圖像的首地址,等效於上面程序中的一種寫法以下:spa
uchar *p=img.data; //首行首地址 *p += img.step; //次行首地址 ……
只要對對C++稍有了解,就知道迭代器是專門用於遍歷數據集合的一種很是重要的特殊的類,用其遍歷隱藏了在給定集合上元素迭代的具體實現方式。C++的STL爲每一個容器類型都提供了迭代器,OpenCV一樣爲cv::Mat提供了與STL迭代器兼容的迭代器。指針
cv::Mat實例的迭代器能夠經過建立一個cv::MatIterator_的實例來獲得,因爲這是一個模板類,因此在聲明時需指定圖像像素的數據類型。code
cv::MatIterator_<cv::Ver3b> it;
另外可以使用定義在Mat_內部的迭代器類型
cv::Mat_<cv::Verc3b>::iterator it;
在這裏咱們經過操做像素的辦法來實現圖像中心對稱反轉。代碼以下:
#include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> using namespace std; using namespace cv; void Flip(Mat &img) { uchar t; if(img.channels()==1) { Mat_<uchar>::iterator it=img.begin<uchar>(); Mat_<uchar>::iterator itend=img.end<uchar>(); itend--; //經過end成員函數獲得的迭代器已超出集合,因此在這裏自減 for(;it<itend;it++,itend--) { t=*it;*it=*itend;*itend=t; } } else if(img.channels()==3) { Mat_<Vec3b>::iterator it=img.begin<Vec3b>(); Mat_<Vec3b>::iterator itend=img.end<Vec3b>(); itend--; for(;it<itend;it++,itend--) for(int k=0; k<3; k++) { t=(*it)[k];(*it)[k]=(*itend)[k];(*itend)[k]=t; } } } int main() { Mat img1=imread("test.jpg"); //將任意一張名爲test.jpg的圖片放置於工程文件夾test中 imshow("First",img1); if(!img1.data) { cout<<"error! The image is not built!"<<endl; return -1; } Flip(img1); imshow("Second",img1); waitKey(); return 0; }
效果以下: