Opencv開發筆記五:畫素的讀寫(一)
一、講講什麼是畫素
畫素是指由影象的小方格即所謂的畫素(pixel)組成的,這些小方塊都有一個明確的位置和被分配的色彩數值,這些小方塊內放的數和所放的位置據決定了影象在某個位置所顯示的顏色,比如某一塊區域放的都是數字0(255),則該片區域會顯示出黑色(白色),可以將畫素視為整個影象中不可分割的單位或者是元素,不可分割的意思是它不能夠再切割成更小單位抑或是元素,它是以一個單一顏色的小方格形式存在。所以就可以把影象的畫素對應的放在影象的某個位置看成一個大的矩陣,矩陣中的每個元素就是這些小方格值的大小。相機所說的畫素,其實是最大畫素,畫素是解析度的單位,這個畫素值僅僅是相機所支援的有效最大解析度,要獲取影象的畫素就要獲取矩陣的每一個元素,所有讀寫畫素要求遍歷矩陣的每一個元素。
二、如何訪問畫素
Open提供多種方法來訪問影象的畫素,這裡講三種方式:
(1)cv::Mat的at函式
at()函式對矩陣中某個畫素進行讀取,可以相當方便,但是效率一般
之前知道複製一個圖片可以方便的通過,src.copyTo(dst) 或者 dst = src.clone(); 方便的實現,但是怎麼通過獲取畫素操作呢?
請看下面:
#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main(int argc, char **argv){ Mat srcImage; srcImage = imread("1.png"); if (!srcImage.data){ cout << "讀取圖片錯誤" << endl; } Mat dstImage(srcImage.size(), srcImage.type()); // dst影象和src影象大小型別相同 int channels = srcImage.channels(); // srcImage的通道數 // 遍歷獲取影象的每一個畫素 for (int row = 0; row < srcImage.rows; row++){ for (int col = 0; col < srcImage.cols; col++){ if (channels == 3){ // 讀畫素:讀取src的每一個通道的每一個畫素 float b = srcImage.at<Vec3b>(row, col)[0]; // 相當於float b = srcImage.at<uchar>(row, col * 3); float g = srcImage.at<Vec3b>(row, col)[1]; // 相當於float g = srcImage.at<uchar>(row, col * 3 + 1); float r = srcImage.at<Vec3b>(row, col)[2]; // 相當於float r = srcImage.at<uchar>(row, col* 3 + 2 ); dstImage.at<Vec3b>(row, col)[0] = b; // 寫畫素:寫入到一張大小型別相同的圖片 dstImage.at<Vec3b>(row, col)[1] = g; dstImage.at<Vec3b>(row, col)[2] = r; } else if(channels == 1){ int ch_value = srcImage.at<uchar>(row, col); dstImage.at<uchar>(row, col) = ch_value; } } } namedWindow("src", CV_WINDOW_AUTOSIZE); namedWindow("dst", CV_WINDOW_AUTOSIZE); imshow("src", srcImage); imshow("dst", dstImage); waitKey(0); return 0; }
再看下面的方式,僅僅是通道數不同,目標生成的圖片顏色就很大的不同
// 建立的單通道的影象 int main(int argc, char **argv){ Mat srcImage = Mat::zeros(Size(300, 300), CV_8UC1); // 建立一副單通道黑色圖片,相當於當通道值為0 Mat dstImage(srcImage.size(), srcImage.type()); // dst影象和src影象大小型別相同 int channels = srcImage.channels(); // srcImage的通道數 // 遍歷獲取影象的每一個畫素 for (int row = 0; row < srcImage.rows; row++){ for (int col = 0; col < srcImage.cols; col++){ if (channels == 3){ dstImage.at<Vec3b>(row, col)[0] = 0; // 寫畫素b dstImage.at<Vec3b>(row, col)[1] = (row+col)/2; // 寫畫素g dstImage.at<Vec3b>(row, col)[2] = 0; // 寫畫素r } else if (channels == 1){ dstImage.at<uchar>(row, col) = (row + col) / 2; // 單通道畫素寫入 } } } namedWindow("src", CV_WINDOW_AUTOSIZE); namedWindow("dst", CV_WINDOW_AUTOSIZE); imshow("src", srcImage); imshow("dst", dstImage); waitKey(0); return 0; } // 建立的是三通道的彩色圖片 int main(int argc, char **argv){ Mat srcImage = Mat::zeros(Size(300, 300), CV_8UC1); // 建立一副3通道黑色圖片 相當於 b=0 g=0 r=0 Mat dstImage(srcImage.size(), srcImage.type()); // dst影象和src影象大小型別相同 int channels = srcImage.channels(); // srcImage的通道數 // 遍歷獲取影象的每一個畫素 for (int row = 0; row < srcImage.rows; row++){ for (int col = 0; col < srcImage.cols; col++){ if (channels == 3){ dstImage.at<Vec3b>(row, col)[0] = 0; // 寫畫素b dstImage.at<Vec3b>(row, col)[1] = (row+col)/2; // 寫畫素g dstImage.at<Vec3b>(row, col)[2] = 0; // 寫畫素r } else if (channels == 1){ dstImage.at<uchar>(row, col) = (row + col) / 2; // 單通道畫素寫入 } } } namedWindow("src", CV_WINDOW_AUTOSIZE); namedWindow("dst", CV_WINDOW_AUTOSIZE); imshow("src", srcImage); imshow("dst", dstImage); waitKey(0); return 0; }
src的圖片都是
dst的圖片前者:
dst的圖片後者:
(1)ptr指標訪問
Vec3b * cur = srcImage.ptr<Vec3b>(i); // 訪問第i行的首元素的指標 用於多通道 Vec3b可以換成具體的型別 看下方表格
uchar* cur = srcImage.ptr<uchar>(i); // 訪問第i行的首元素的指標 用於單通道
int main(int argc, char **argv){
Mat srcImage = Mat::zeros(Size(300, 300), CV_8UC3); // 建立一副單通道白色圖片
int channels = srcImage.channels(); // srcImage的通道數
// 遍歷獲取影象的每一個畫素
for (int row = 0; row < srcImage.rows; row++){
Vec3b * cur = srcImage.ptr<Vec3b>(row); // 獲取當前row行第一個元素的指標 由於是3通道需用Vec3b型別儲存
uchar* p = srcImage.ptr<uchar>(row); // 獲取第row行第一個畫素的指標 用於單通道
for (int col = 0; col < srcImage.cols; col++){
if (channels == 3){
cur[col][0] = row % 255; //Blue
cur[col][1] = col % 255; //Gree
cur[col][2] = 0; //Red
}
if (channels == 1){
p[col] = (row + col) % 255;
}
}
}
namedWindow("src", CV_WINDOW_AUTOSIZE);
imshow("src", srcImage);
waitKey(0);
return 0;
}
opencv中有模板類Vec,可以表示一個向量
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
(3)迭代器訪問
Opencv 的迭代器 提供了
MatIterator_<uchar> iter_gray; // 訪問單通道
MatIterator_<Vec3b> iter_color; // 訪問3通道 Vec3b可以修改 具體看影象型別 檢視Vec類
// 迭代器訪問
int main(int argc, char **argv){
{
RNG rng(123);
Mat gray(300, 300, CV_8UC1); //建立一個大小為300x300的單通道灰度圖
Mat color(300, 300, CV_8UC3); //建立一個大小為300x300的三通道彩色圖
MatIterator_<uchar> iter_gray;
for (iter_gray = gray.begin<uchar>(); iter_gray != gray.end<uchar>(); iter_gray++){
*iter_gray = rng.uniform(0, 255) % 255;
}
MatIterator_<Vec3b> iter_color;
for (iter_color = color.begin<Vec3b>(); iter_color != color.end<Vec3b>(); iter_color++){
(*iter_color)[0] = rng.uniform(0, 255) % 255; // b
(*iter_color)[1] = rng.uniform(0, 255) % 255; // g
(*iter_color)[2] = rng.uniform(0, 255) % 255; // r
}
namedWindow("gray", CV_WINDOW_AUTOSIZE);
imshow("gray", gray);
imshow("color", color);
waitKey(0);
return 0;
}