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的图片都是Opencv开发笔记五:像素的读写(一)

 dst的图片前者:Opencv开发笔记五:像素的读写(一)

 dst的图片后者:Opencv开发笔记五:像素的读写(一)

(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;
}