基础入门
在OpenCV中,像素是最基本的操作单位。图像可以视为一个三维数组,其中第三维表示颜色通道。图像数据在内存中以连续或几乎连续的方式存储,对于多通道图像(比如:BGR图像),每个像素的各通道值紧密排列。OpenCV主要使用BGR色彩空间,与常用的RGB顺序不同。因此,在进行像素操作时,需要特别注意色彩通道的顺序。OpenCV中最常见的图像格式是CV_8UC3,表示一个8位无符号整型的三通道图像。
像素操作通常会涉及到颜色,在OpenCV中,Scalar类型常用来表示颜色。一个Scalar对象可以存储四个元素,分别对应于图像中的四个通道,通常为BGRA色彩空间。当处理彩色图像时,这四个值代表蓝色(B)、绿色(G)、红色(R)和可选的透明度(A,alpha通道)。如果处理的是灰度图像,则通常只使用第一个通道即可。
如果我们想定义一个红色的颜色,可以参考下面的示例代码。
// BGR格式,故(0, 0, 255)代表红色
cv::Scalar redColor(0, 0, 255);
对于带有透明度的颜色,可以像下面的示例代码这样,定义一个半透明的红色。
// 最后一个值是alpha通道,范围从0(完全透明)到255(完全不透明)
cv::Scalar semiTransparentColor(0, 0, 255, 128);
实战解析
在OpenCV中,主要有两种方式来访问和修改像素值:指针访问和at函数访问。
指针访问是指直接通过计算像素地址来进行操作,这种方式在性能上可能更优,但实现较为复杂,容易出错。在下面的示例代码中,我们创建了一个300 x 400像素的蓝色图像,并通过指针访问方式将图像最中间一行的像素修改为红色。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 创建一个300 x 400的蓝色图像
Mat img(300, 400, CV_8UC3, Scalar(255, 0, 0));
// 访问并修改最中间一行的像素,将其变为红色
int nMidRow = img.rows / 2;
uchar* pRow = img.ptr<uchar>(nMidRow);
for (int nCol = 0; nCol < img.cols; nCol++)
{
// B分量
pRow[nCol * 3] = 0;
// G分量
pRow[nCol * 3 + 1] = 0;
// R分量
pRow[nCol * 3 + 2] = 255;
}
// 显示图像
imshow("Image", img);
waitKey(0);
return 0;
}
at函数访问提供了一种更安全、更易读的方式来访问和修改像素值,虽然牺牲了一点性能,但代码更加清晰。在下面的示例代码中,我们同样创建了一个300 x 400像素的蓝色图像,并通过at函数访问方式将图像最中间一行的像素修改为红色。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 创建一个300 x 400的蓝色图像
Mat img(300, 400, CV_8UC3, Scalar(255, 0, 0));
// 访问并修改最中间一行的像素,将其变为红色
int nMidRow = img.rows / 2;
for (int nCol = 0; nCol < img.cols; nCol++)
{
// 通过at函数访问每个像素并修改其颜色分量
img.at<Vec3b>(nMidRow, nCol)[0] = 0;
img.at<Vec3b>(nMidRow, nCol)[1] = 0;
img.at<Vec3b>(nMidRow, nCol)[2] = 255;
}
// 显示图像
imshow("Image", img);
waitKey(0);
return 0;
}
执行上面的示例代码,运行效果可参考下图。
均值计算
在OpenCV中,计算图像的像素均值,主要利用cv::mean()函数。像素均值被计算出来后,可以作为图像亮度调整的基础。
Mat img;
Scalar avgPixel = cv::mean(img);
cout << avgPixel[0] << ", " << avgPixel[1] << ", " << avgPixel[2] << endl;
像素级逻辑操作
像素级逻辑操作通常涉及按位与、或、异或等操作,主要用于图像处理中的掩码应用、图像合成等场景。OpenCV提供了一系列函数来执行像素级的逻辑操作,下面逐一进行介绍。
1、逻辑与。cv::bitwise_and()函数用于对两个图像的对应像素执行逻辑与操作。如果两个像素都为非零,结果像素就是非零;否则,结果像素为零。这对于应用掩码、选取图像的交集区域非常有用。
2、逻辑或。cv::bitwise_or()函数用于对两个图像的对应像素执行逻辑或操作。只要两个像素中有一个为非零,结果像素就是非零。这可以用来合并图像的区域,或增加特征检测的鲁棒性。
3、逻辑异或。cv::bitwise_xor()函数用于对两个图像的对应像素执行逻辑异或操作。当两个对应像素不同时,结果像素为非零;否则,结果像素为零。这可以用于突出显示两个图像之间的差异。
4、逻辑非。cv::bitwise_not()函数用于对图像中的每个像素执行逻辑非操作,即将1变为0,0变为1。这常用于图像的反转,或颜色空间转换前的预处理。
注意:在使用以上这些函数时,需要确保输入图像的数据类型兼容,并且大小相同。此外,还可以传入一个可选的掩码参数,仅对掩码中非零的像素执行操作,这对于局部处理非常有用。
接下来,我们通过下面的实战代码来理解cv::bitwise_and()函数。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("OpenCV.png");
if(srcImage.empty())
{
cout << "Image not found" << endl;
return -1;
}
// 创建一个矩形的掩码图像,初始化为全黑
Mat mask = Mat::zeros(srcImage.size(), CV_8UC1);
// 画一个白色矩形作为掩码
rectangle(mask, Point(150, 0), Point(450, 250), Scalar(255, 255, 255), -1);
// 使用bitwise_and函数应用掩码
Mat destImage;
bitwise_and(srcImage, srcImage, destImage, mask);
// 显示原图、掩码和结果
imshow("Original Image", srcImage);
imshow("Mask", mask);
imshow("Result Image", destImage);
waitKey(0);
destroyAllWindows();
return 0;
}
在上面的示例代码中,我们首先读取了一张图片到srcImage中。接着,我们创建了一个与原图同样大小的单通道灰度图像mask,并用一个白色矩形填充了其中一部分。这个白色矩形就是我们的“感兴趣区域”,其余部分为黑色,代表透明或不需要的部分。
通过bitwise_and函数,我们将原图srcImage与自身进行了按位与操作,并且指定了掩码mask。这意味着只有掩码中为白色(值为255)的部分,在结果图像中保留了原图的像素值。而掩码中为黑色(值为0)的部分,在结果图像中对应的像素值将被设为0(黑色),从而达到了只显示我们感兴趣区域的效果。
最后,我们使用imshow显示了原图、掩码、应用掩码后的结果图像,可参考如下。