写在前面
做CV领域的小伙伴,想必都要跟图像像素的运算打交道,本文将介绍几种访问图像像素的方法,并用一个简单的例子进行演示。
先修知识
CV_8U is unsigned 8bit/pixel - ie a pixel can have values 0-255, this is the normal range for most image and video formats.
CV_32F is float - the pixel can have any value between 0-1.0, this is useful for some sets of calculations on data - but it has to be converted into 8bits to save or display by multiplying each pixel by 255.
CV_32S is a signed 32bit integer value for each pixel - again useful of you are doing integer maths on the pixels, but again needs converting into 8bits to save or display. This is trickier since you need to decide how to convert the much larger range of possible values (+/- 2billion!) into 0-255.
一般使用的灰度图像元素是8位的uchar
型(注意不是无符号整型,而是无符号字符型。元素值0~255),自定义Mat
矩阵时,推荐使用8位无符号字符型,即CV_8U、CV_8UC31。
一般的图像文件格式使用的是 Unsigned 8bits,CvMat矩阵对应的参数类型就是
CV_8UC1,CV_8UC2,CV_8UC3。
(最后的1、2、3表示通道数,譬如RGB3通道就用CV_8UC3)
而float 是32位的,对应CvMat数据结构参数就是:CV_32FC1,CV_32FC2,CV_32FC3…
double是64bits,对应CvMat数据结构参数:CV_64FC1,CV_64FC2,CV_64FC3等。
使用at()函数访问像素值
使用cv::Mat
定义的图像是向量,可以使用at
方法取值,使用调用方法image.at<cv::Vec3b>(i,j)
,at方法方便,直接给i、j
赋值就可以随意访问图像中任何一个像素,其中i
表示第i
行,j
表示该行第j
个像素。
但是at
方法效率是这几种访问方法中最慢的一个,所以如果遍历图像或者访问像素比较多时,不建议使用这个方法,毕竟程序的效率还是比程序的可读性要重要的2。
使用Mat的成员函数ptr< >( )访问像素值
该方法使用到指针
, cv::Mat
中提供ptr
函数访问任意一行像素的首地址,特别方便图像一行一行地横向访问。
ptr访问效率比较高,程序也比较安全,有越界判断。
案例演示
下面用一个例子来对这几种访问图像像素值的方法进行演示,本例子将演示图像的像素值置为全黑、置为全白以及灰度化处理,代码如下:
#include<opencv2/opencv.hpp>
int main() {
cv::Mat image = cv::imread("./opencv_logo.png");
// 对三通道图像进行各个像素的访问
#if 0
for (int i = 0; i < image.rows; i++) {
for (int j = 0; j < image.cols; j++) {
for (int n = 0; n < image.channels(); n++) {
image.at<cv::Vec3b>(i, j)[n] = 155;
}
}
}
cv::imwrite("./logo_after.png", image);
#endif
#if 0
// 对单通道图像进行各个像素的访问
cv::Mat image_gray;
cv::cvtColor(image, image_gray, cv::COLOR_BGR2GRAY);
cv::imwrite("./logo_gray.png", image_gray);
for (int i = 0; i < image_gray.rows; i++) {
for (int j = 0; j < image_gray.cols; j++) {
image_gray.at<uchar>(i, j) = 0;
}
}
cv::imwrite("./logo_gray_after.png", image_gray);
#endif
#if 0
//通过指针访问来修改图像的像素值
for (int i = 0; i < image.rows; i++) {
uchar *data = image.ptr<uchar>(i); //ptr函数访问任意一行像素的首地址,特别方便图像地一行一行的横向访问
for (int j = 0; j < image.cols*image.channels(); j++) { // //在循环体内,应该避免多次运算,应该提前算cols*channels
data[j] = 255;
}
}
cv::imwrite("./logo_ptr_white.png", image);
#endif
#if 1
// 案例二,使用指针将彩色图转为灰度图
cv::Mat grayImage = cv::Mat(image.rows, image.cols, CV_8U); // 生成和原图image大小相同的空白灰度图像
for (int i = 0; i < image.rows; i++) {
// 指针,用来获取每一行的首地址
cv::Vec3b *p = image.ptr<cv::Vec3b>(i); // p 指向地址, *p指向内容
uchar *p2 = grayImage.ptr<uchar>(i);
for (int j = 0; j < image.cols; j++) {
// 每次迭代获取图像列的地址
cv::Vec3b &pix = *p++; // pix 指向内容, &pix 指向地址
uchar &pix2 = *p2++;
pix2 = pix[0] * 0.114 + pix[1] * 0.587 + pix[2] * 0.299; // pix[0] 对应B通道,pix[1]对应G通道,pix[2]对应R通道 Gray = R*0.299 + G*0.587 + B*0.114
}
}
cv::imwrite("./logo_ptr_gray.png", grayImage);
#endif
return 0;
}
写到这里,差不多本文也就要结束了,如有错误,敬请指正。如果我的这篇文章帮助到了你,那我也会感到很高兴,一个人能走多远,在于与谁同行。