目录
图像处理中的操作都是从像素开始的,在OpenCV中,提供了三种访问每个像素的方法:
- 指针访问:C操作符[];
- 迭代器iterator;
- 动态地址计算;
这三种方法在访问速度上略有差异。debug模式下,差异比较明显,release模式下,差异不大。下面通过具体的代码来说明这几种方法。
下面程序的主要目的是减少图像中颜色的数量,比如原来的图像是256中颜色,我们要将其变为64中颜色,只需要将原来的颜色除以4(整除)以后再乘以4就可以了。
主程序:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void colorReduce(Mat& inputImage, Mat& outputImage, int div);
int main() {
std::cout << "Hello, World!" << std::endl;
Mat image = imread("/Users/dwz/Desktop/cpp/1.jpg");
// imshow("原图",image);
cout << image.size() << endl;
cout << image.rows << endl;
cout << image.cols << endl;
cout << image.channels() << endl;
cout << image.type() << endl;
// Mat dstImage(image.rows, image.cols, CV_8UC3);
Mat dstImage;
dstImage.create(image.rows, image.cols, image.type());
double time0 = static_cast<double>(getTickCount());
colorReduce(image, dstImage, 32);
time0 = ((double)getTickCount() - time0) / getTickFrequency();
cout << time0 << endl;
imshow("效果图", dstImage);
waitKey(0);
return 0;
}
主程序中调用colorReduce函数来完成减少颜色的工作,我们根据访问像素的三类方法实现了三个colorReduce函数,下面分别进行讲解。
一、用指针访问像素
Mat类又若干成员函数可以获取图像的属性。公寓哦成员变量cols和rows给出了图像的宽和高,而成员函数channels()用于返回图像的通道数。灰度图像的通道数等于1,彩色图像的通道数等于3.
每行的像素值由一下语句得到:
void colorReduce(Mat& inputImage, Mat& outputImage, int div){
outputImage = inputImage.clone(); // 复制实参到临时变量
int rowNum = outputImage.rows; // 图像的行数
int colNum = outputImage.cols * outputImage.channels(); // 图像的列数 * 通道数 = 每一行元素的个数
for (int i=0;i<rowNum;i++)
{
// 获取第i行的首地址
uchar* data = outputImage.ptr<uchar>(i);
for (int j=0;j<colNum;j++)
{
// 处理每个像素
data[j] = data[j]/div *div + div/2;
}
}
}
为了简化指针运算,Mat类提供了ptr函数可以得到图像任意行的首地址。ptr是一个模板函数,它返回第i行的首地址:
uchar* data = outputImage.ptr<uchar>(i);
内部循环时处理像素,我们可以等效的使用指针运算从一列移动到下一列:
*data++=*data/div*div+div/2;
二、用迭代器操作像素
用迭代器操作像素,我们需要获取图像矩阵的begin和end,然后增加迭代从begin到end,将*操作符添加在迭代指针前,即可访问当前指向的内容。
相比用指针直接访问可能出现越界问题,迭代器是非常安全的方法。
void colorReduce(Mat& inputImage, Mat& outputImage, int div){
outputImage = inputImage.clone();
Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();
Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>();
for (; it != itend; ++it) {
(*it)[0] = (*it)[0] / div *div+div/2;
(*it)[1] = (*it)[1] / div *div+div/2;
(*it)[2] = (*it)[2] / div *div+div/2;
}
}
三、动态地址计算
用动态地址计算来操作像素需要配合at方法,这种方法简单明了,符合我们对像素的直观认识。
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
outputImage = inputImage.clone();
int rowNum = outputImage.rows;
int colNum = outputImage.cols;
for (int i=0; i<rowNum;i++)
{
for (int j=0;j<colNum;j++)
{
outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0]/div*div + div/2;
outputImage.at<Vec3b>(i, j)[1] = outputImage.at<Vec3b>(i, j)[1]/div*div +div/2;
outputImage.at<Vec3b>(i, j)[2] = outputImage.at<Vec3b>(i, j)[2]/div*div + div/2;
}
}
}
Mat类中的cols和rows可以得到图像的行数和列数,成员函数at(int y, int x)用来存取图像像素,但是必须知道图像的数据类型,因为at不会对数据类型进行转换。
Vec3b表示由三个unsigned char组成的向量。