在opencv的編程中,遍歷訪問圖像元素是經常遇到的操作,掌握其方法非常重要,無論是Mat類的像素訪問,還是IplImage結構體的訪問的方法,都必須扎實掌握,畢竟,圖像處理本質上就是對像素的各種操作,訪問元素就是各種圖像處理算法的第一步。
首先先看看圖像的是怎么存儲的。
單通道圖像
多通道圖像
Mat訪問圖像元素方法匯總
1.用指針訪問元素
在大多數圖像處理任務中, 執行計算時你都需要對圖像的所有像素進行掃描。 當需要訪問的像素數量非常龐大, 你必須采用高效的方式來執行這個任務來提高效率。 如果你需要高效掃描大圖片的數據,那么請使用指針方式。
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat img = imread("lena.jpg", 1);
if (img.empty())
{
cout << "fail to read image" << endl;
return -1;
}
Mat img1 = img.clone();
int div = 64;
/* 方法1:用指針訪問 */
//多通道訪問法1
int rows = img1.rows;
int cols = img1.cols;
for (int i = 0; i < rows; i++)
{
//uchar* p = img1.ptr<uchar>(i); //獲取第i行的首地址
for (int j = 0; j < cols; j++)
{
//在這里操作具體元素
uchar *p = img1.ptr<uchar>(i, j);
p[0] = p[0] / div*div + div / 2;
p[1] = p[1] / div*div + div / 2;
p[2] = p[2] / div*div + div / 2;
}
}
imshow("lean", img1);
//多通道訪問法2
Mat img3 = img.clone();
int channels = img3.channels(); //獲取通道數
int rows3 = img3.rows;
int cols3 = img3.cols* channels; //注意,是列數*通道數
for (int i = 0; i < rows3; i++)
{
uchar* p = img3.ptr<uchar>(i); //獲取第i行的首地址
for (int j = 0; j < cols3; j++)
{
//在這里操作具體元素
p[j] = p[j] / div*div + div / 2;
p[j+1] = p[j+1] / div*div + div / 2;
p[j+2] = p[j+2] / div*div + div / 2;
}
}
imshow("lean3", img3);
//單通道圖像
Mat img2 = img.clone();
cvtColor(img2, img2, COLOR_BGR2GRAY);
for (int i = 0; i < img2.rows; i++)
{
uchar* p = img2.ptr<uchar>(i); //獲取第i行的首地址
for (int j = 0; j < img2.cols; j++)
{
//在這里操作具體元素
p[j] = p[j] / div*div + div / 2;
}
}
imshow("lean2", img2);
waitKey(0);
return 0;
}
2.用迭代器訪問元素
在面向對象編程時, 我們通常用迭代器對數據集合進行循環遍歷。 標准模板庫(STL) 對每個集合類都定義了對應的迭代器類, OpenCV也提供了cv::Mat的迭代器類, 並且與C++ STL中的標准迭代器兼容。
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat img = imread("lena.jpg",1); //載入灰度圖
Mat img1 = img.clone();
int div = 64;
/* 方法2:用迭代器訪問 */
/******************多通道的可以這么寫***************/
Mat_<Vec3b>::iterator it = img1.begin<Vec3b>(); //獲取起始迭代器
Mat_<Vec3b>::iterator it_end = img1.end<Vec3b>(); //獲取結束迭代器
for (; it != it_end; it++)
{
//在這里分別訪問每個通道的元素
(*it)[0] = (*it)[0] / div*div + div / 2;
(*it)[1] = (*it)[1] / div*div + div / 2;
(*it)[1] = (*it)[1] / div*div + div / 2;
}
imshow("lean", img1);
/******************單通道的可以這么寫***************/
Mat img2;
cvtColor(img, img2, COLOR_RGB2GRAY); //轉化為單通道灰度圖
Mat_<uchar>::iterator it2 = img2.begin<uchar>(); //獲取起始迭代器
Mat_<uchar>::iterator it_end2 = img2.end<uchar>(); //獲取結束迭代器
for (; it2 != it_end2; it2++)
{
//在這里分別訪問每個通道的元素
*it2 = *it2 / div*div + div / 2;
}
imshow("lena2", img2);
waitKey(0);
return 0;
}
}
若要從圖像的第二行開始,程序該怎么修改? 我們可以用
image.begin<cv::Vec3b>()+image.cols
初始化cv::Mat迭代器。 獲得集合結束位置的方法也類似, 只是改用end方法。 但是, 用end方法得到的迭代器已經超出了集合范圍, 因此必須在結束位置停止迭代過程。 結束的迭代器也能使用數學計算, 例如, 如果你想在最后一行前就結束迭代, 可使用
image.end<cv::Vec3b>()-image.cols
3.動態地址+at()訪問元素
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat img = imread("lena.jpg",1);
Mat img1 = img.clone();
int div = 64;
/* 方法3:用at訪問 */
/****************訪問多通道元素*********************/
int rows = img1.rows;
int cols = img1.cols;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
//在這里訪問每個通道的元素,注意,成員函數at(int y,int x)的參數
img1.at<Vec3b>(i,j)[0] = img1.at<Vec3b>(i, j)[0] / div*div + div / 2;
img1.at<Vec3b>(i, j)[1] = img1.at<Vec3b>(i, j)[1] / div*div + div / 2;
img1.at<Vec3b>(i, j)[2] = img1.at<Vec3b>(i, j)[2] / div*div + div / 2;
}
}
imshow("lena", img1);
/****************訪問單通道元素*********************/
Mat img2;
cvtColor(img, img2, COLOR_RGB2GRAY);
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
//在這里訪問每個通道的元素,注意,成員函數at(int y,int x)的參數
img2.at<uchar>(i, j) = img2.at<uchar>(i, j) / div*div + div / 2;
}
}
imshow("lena2", img2);
waitKey(0);
return 0;
}
IplImage訪問元素方法匯總
1.使用cvGet2D()函數訪問
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
/*訪問單通道元素*/
IplImage* img = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1); //單通道圖像
CvScalar s;
double tmp;
for (int i = 0; i < img->height; i++)
{
for (int j = 0; j < img->width; j++)
{
//可以在這里訪問元素
tmp = cvGet2D(img, i, j).val[0];
cvSet2D(img, i, j, 255); //第三個參數是要設置的值
}
}
cvShowImage("img", img);
/*訪問多通道元素*/
IplImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);
double tmpb, tmpg, tmpr;
for (int i = 0; i < img->height; i++)
{
for (int j = 0; j < img->width; j++)
{
tmpb = cvGet2D(img, i, j).val[0];
tmpg = cvGet2D(img, i, j).val[1];
tmpr = cvGet2D(img, i, j).val[2];
cvSet2D(img2, i, j, CvScalar(255,255,255)); //第三個參數是要設置的值,三個通道一起設置
}
}
cvShowImage("img2", img2);
waitKey(0);
return 0;
}
2.指針方式直接訪問
追求高效率地訪問元素請使用該方法。
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
/*訪問多通道元素*/
IplImage* img = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 3);
uchar* data = (uchar *)img->imageData;
int step = img->widthStep / sizeof(uchar);
int channels = img->nChannels;
uchar b, g, r;
for (int i = 0; i < img->height; i++)
{
for (int j = 0; j < img->width; j++)
{
//獲得元素的值
b = data[i*step + j*channels + 0];
g = data[i*step + j*channels + 1];
r = data[i*step + j*channels + 2];
//修改元素的值
data[i*step + j*channels + 0] = 255;
}
}
cvShowImage("img", img);
/*訪問單通道元素*/
IplImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
uchar* data2 = (uchar *)img2->imageData;
int step2 = img2->widthStep / sizeof(uchar);
uchar v;
for (int i = 0; i < img2->height; i++)
{
for (int j = 0; j < img2->width; j++)
{
//獲得元素的值
v = data2[i*step2 + j];
//修改元素的值
data2[i*step2 + j] = 255;
}
}
cvShowImage("img2", img2);
waitKey(0);
return 0;
}