一、访问像素值
【准备工作】为了说明如何直接访问像素值,将创建一个简单的函数,随机选择一些像素,把它们设置为白色。
【实现】记得添加头文件 #include
1. 使用循环,每次把随机选择的像素设置为255,即白色。这里使用了 type 来区分灰度图像和彩色图像。
void salt(cv::Mat image, int n) {
//C++11 的随机数生成器
std::default_random_engine generator;
std::uniform_int_distribution
randomRow(0, image.rows - 1);
std::uniform_int_distribution
randomCol(0, image.cols - 1);
int i, j;
for (int k = 0; k < n; k++) {
//随机生成图形位置
i = randomCol(generator);
j = randomRow(generator);
if (image.type() == CV_8UC1) {//灰度图像
//单通道8位图像
image.at(j, i) = 255;
}
else if (image.type() == CV_8UC3) {//彩色图像
//3通道图像
image.at(j, i)[0] = 255;
image.at(j, i)[1] = 255;
image.at(j, i)[1] = 255;
}
}
}
2. 传入图像,调用函数
cv::Mat image;
image = cv::imread("1.jpg");
salt(image, 3000);
cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(0);
【实现原理】
1. 变量 cols 和 rows 可得到图像的列数和行数;cv::Mat 的 at(int y,int x)方法可以访问元素,其中 x 是 列号,y 是行号。在调 用 at 方法时,必须指定图像元素的类型,且保证指定的类型与矩阵内的类型是一致的。
2. 彩色图像有红色通道、绿色通道和蓝色通道,因此包含彩色图像的 cv::Mat 类会返回一个向量,向量中包含三个 8 位的数值。OpenCV 为这样的短向量定义了一种 类型,即 cv::Vec3b。这个向量包含三个无符号字符(unsigned character)类型的数据。
3. 类似的向量类型表示二元素向量和四元素向量:cv::Vec2b 和 cv::Vec4b。此外还有针对其他元素类型的向量。例如,表示二元素浮点数类型的向量就是把类型名称的最后一个字母换成 f,即 cv::Vec2f。对于短整型,最后的字母换成 s;对于整型,最后的字母换成 i; 对于双精度浮点数向量,最后的字母换成 d。所有这些类型都用 cv::Vec模板类定义,其 中 T 是类型,N 是向量元素的数量。
【扩展阅读】
可以用 operator() 直接访问矩阵的元素。使用操作符 operator()和使用 at 方法产生的结果是完全相同的, 只是前者的代码更简短。
// 用 Mat 模板操作图像
cv::Mat_ img(image);
img(50,100)= 0; // 访问第 50 行、第 100 列处那个值
二、用指针扫描图像
【准备工作】减少图像中颜色的数量。将图像中每个像素的值除以 N(这里假定使用整数除法,不保留余数)。然后将结果乘以 N,得到 N 的倍数,并且刚好不超过原始像素值。加上 N / 2,就得到相邻的 N 倍数之间的中间值。
【实现】
1. 处理函数
void colorReduce(cv::Mat image, int div = 64) {
int n1 = image.rows;//行数
//每行的元素数量
int nc = image.cols * image.channels();
for (int j = 0; j < n1; j++) {
//取得行j的地址
uchar* data = image.ptr(j);
for (int i = 0; i < nc; i++) {
//处理每个像素---------------
data[i] = data[i] / div * div + div / 2;
//像素处理结束---------------
}//一行结束
}
}
2. 测试函数
cv::Mat image;
image = cv::imread("1.jpg");
colorReduce(image, 64);
cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(0);
【实现原理】
1. 获得每一行中像素值的个数:int nc= image.cols * image.channels();
2. 返回第 j 行的地址:uchar* data= image.ptr(j);
3. 也可以利用指针运算从一列移到下一列:*data++= *data/div*div + div2;
【我的疑问】
data[i] / div * div + div / 2 ; 这里除了又乘了后,为什么会变化,而且又加了二分之一的div
对于下面的扩展阅读就更懵了
【扩展阅读】
1. 其他减色算法(位运算效率最高)
//以使用取模运算符,它可以直接得到 div 的倍数
data[i]= data[i] – data[i]%div + div/2;
//位运算符,截取像素值的掩码
uchar mask= 0xFF<>1; // 加上 div/2
// 这里的+也可以改用“按位或”运算符
2. 使用输入和输出函数
若不希望对原始图像进行修改,因此调用函数前要先备份图像
cv::Mat image;
image = cv::imread("1.jpg");
//复制图像,深复制最简单的是使用clone
cv::Mat imageClone = image.clone();
//处理图像副本
colorReduce(imageClone, 64);
cv::namedWindow("Image");
cv::imshow("Image", imageClone);
cv::waitKey(0);
若允许用户选择是否就地处理
void colorReduce(const cv::Mat &image, // 输入图像
cv::Mat &result, // 输出图像
int div=64);
PS:输入图像是一个引用的 const,表示图像不会在函数中修改。输出图像是一个引用参数,在函数中会被修改,并且返回给调用这个函数的代码。
//就地处理
colorReduce(image,image);
//否则
cv::Mat result;
colorReduce(image,result);
函数中首先要调用 create 方法,构建一个大小和类型都与输入图像相同的矩阵(如果必要)
result.create(image.rows,image.cols,image.type());
//分配的内存块的大小表示为 total()*elemSize()
//扫描过程
for (int j=0; j(j);
uchar* data_out= result.ptr(j);
for (int i=0; i
3. 对连续图像的高效扫描
//---------------测试矩阵的连续性----------------
// 检查行的长度(字节数)与“列的个数×单个像素”的字节数是否相等
image.step == image.cols*image.elemSize();
//-------isContinuous 方法检查矩阵的连续性-------
//处理图像可以改为
void colorReduce(cv::Mat image, int div=64) {
int nl= image.rows; // 行数
// 每行的元素总数
int nc= image.cols * image.channels();
if (image.isContinuous()) {
// 没有填充的像素
nc= nc*nl;
nl= 1; // 它现在成了一个一维数组
}
int n= staic_cast(
log(static_cast(div))/log(2.0) + 0.5);
// 用来截取像素值的掩码
uchar mask= 0xFF<> 1; // div2 = div/2
// 对于连续图像,这个循环只执行一次
for (int j=0; j(j);
for (int i=0; i
4. 低层次指针算法
在 cv::Mat 类中,图像数据是存放在无符号字符型的内存块中的。其中 data 属性表示内存块第一个元素的地址,它会返回一个无符号字符型的指针。
//从图像的起点开始循环
uchar *data= image.data;
//利用有效宽度来移动行指针,可以从一行移到下一行
data+= image.step; // 下一行
//用 step 属性可得到一行的总字节数(包括填充像素)
// (j,i)像素的地址,即&image.at(j,i)
data= image.data+j*image.step+i*image.elemSize();
//不推荐使用