序言: OpenCV3编程入门已经进入第五章 (Core组件进阶) 学习了,后面也越来越多对图像的处理,这一节主要是对像素的认识。
图像矩阵的大小取决于所用的颜色模型,也就是说取决于所用的通道数。如下灰度图像:
对于多通道的图像,矩阵中的列会包含多个子列,子列个数与通道数相等。
(1)问题提出:对于单通道像素,有256个不同的值,若三通道图像,颜色的存储值就有一千六百万多种。
解决: 如此之多的颜色数值,但是达到同样效果的只占很小一部分。颜色空间缩减(color space reduction)可以大大降低运算复杂度。它的做法:将现有颜色空间值除以某个输入值,以获得较少的颜色数,比如:0-9可以取0,10-19取10,以此类推。
(2)uchar类型的三通道图像,每个通道取值0~255,也就是256x256不同的值。
这样操作,将颜色取值降低为26x26x26种情况。即:Inew=(Iold/10)*10。因为uchar类型的值除以int值,结果仍是char类型,所以求出来的小数也要向下取整。
(3)为了防止处理图像像素时,每一步计算都会花销时间,于是可以把计算好的结果提前存入表table中,这样不需要计算,直接取结果即可。
// 1、遍历图像矩阵的每一个像素
// 2、对像素应用上述公式
int divideWith=10;
uchar table[256];
for(int i=0;i<256;i++){
table[i]=divideWith*(i/divideWith);//table[i]存放值为i像素减小颜色空间结果
}
//p[j]=table[p[j]];
总结:对于较大的图像,有效的方法是预先计算所有可能的值,然后需要这些值的时候,利用查找表直接赋值即可。
说明:函数原型为:operationsOnArrays:LUT()的函数来进行,用于批量进行图像元素查找、扫描与操作图像。
//首先建立一个Mat型用于查表
Mat lookUpTable(1,256,CV_8U);
uchar* p=lookUpTable.data;
for(int i=0;i<256;i++){
p[i]=table[i];
}
//然后调用函数(I输入,J输出)
for(int i=0;i<times;i++){
LUT(I,lookUpTable,J);
}
这个地方书上讲的有点模糊,不是很懂,就去参考其他大佬
(1)对于单通道图像,其像素灰度值为0-255,LUT函数可以将函数的一个灰度值转换成其他灰度值。如:将一张图片0-100的像素的灰度值变成0,101-200的像素变成100,201-255的像素变成255。
(2)对于多通道图像,其内部每一个通道都做这样的映射,这样每一个通道的处理就和单通道差不多了。
(3)LUT函数为:
void LUT(InputArray src,InpitArray lut,OutputArray dst);
(4)单通道图像处理
#include
using namespace cv;
int main()
{
uchar lutData[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
lutData[i] = 0;
if (i > 100 && i <= 200)
lutData[i] = 100;
if (i > 200)
lutData[i] = 255;
}
Mat lut(1, 256, CV_8UC1);
Mat a = imread("12.jpg", CV_LOAD_IMAGE_GRAYSCALE),b;
namedWindow("原灰度图像", CV_WINDOW_AUTOSIZE);
namedWindow("转灰度图像", CV_WINDOW_AUTOSIZE);
imshow("原灰度图像", a);
LUT(a, lut, b);
imshow("转灰度图像", b);
waitKey(0);
}
说明:计时函数:getTickCount()和getTickFrequency()
double time0=static_cast<double>(getTickCount());//记录起始时间
//进行图像处理操作。。。。
time0=((double)getTickCount()-time0)/getTickFrequency();
printf("输出运行时间%f\n",time0);
说明:任何图像处理算法,都是从操作每个像素开始的。
提供三种访问每个像素的方法
这三种方法在访问速度上略有差异,在debug模式下,比较明显。程序的目的是减少图像中颜色的数量,比如原来的图像是256种颜色,变成64只需要将原来的颜色除以4再乘以4.
示例程序:主程序中调用colorReduce函数来完成减少颜色的工作。
#include
#include
#include
using namespace cv;
using namespace std;
//全局函数声明
void colorReduce(Mat &inputImage, Mat& outputImage, int div);
int main()
{
Mat srcImage = imread("12.jpg");
imshow("原始图像",srcImage);
//将原始图的参数规格来创建效果图
Mat dstImage;
dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());//效果图的大小、类型和原图片相同
//记录起始时间
double time0 = static_cast<double>(getTickCount());
//调用颜色空间缩减函数
colorReduce(srcImage, dstImage, 128);
//计算运行时间并输出
time0 = ((double)getTickCount() - time0) / getTickFrequency();
printf("此方法运行时间为:%f\n", time0);
imshow("效果图",dstImage);
waitKey(0);
}
说明:用指针访问像素发这种方法利用的是C语言操作符[ ],这种方法比较快。
void colorReduce(Mat &inputImage, Mat& outputImage, int div)
{
//参数准备
outputImage = inputImage.clone();//复制实参到临时变量
int rowNumber = outputImage.rows;//行数
int colNumber = outputImage.cols*outputImage.channels();//列数x通道数=每一行元素的个数
//双重循环,遍历所有的像素值
for (int i = 0; i < rowNumber; i++) {//行循环
uchar* data = outputImage.ptr<uchar>(i);//获取第i行的首地址
for (int j = 0; j < colNumber; j++) { //列循环
//处理每一个像素
data[j] = data[j] / div * div + div / 2;
}
}
}
补充:简化指针运算,Mat类提供了ptr函数可以得到图像任意行的首地址。
uchar* data = outputImage.ptr
(i);//获取第i行的首地址
说明:在迭代法中,获得图像矩阵的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方法的colorReduce函数的代码,这种方法简洁,便于对像素的直观认识。
/*
动态地址法
*/
void colorReduce(Mat &inputImage, Mat& outputImage, int div)
{
//参数准备
outputImage = inputImage.clone();//复制实参到临时变量
int rowNumber = outputImage.rows;//行数
int colNumber = outputImage.cols;//行数
//获取彩色图像像素
for (int i = 0; i < rowNumber; i++) {
for (int j = 0; j < colNumber; j++) {
//开始处理每一个像素,0/1/2表示通道数
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;//红色通道
}
}
}
补充:
- Vec3b:由三个unsigned char组成的向量
- image.at(i,j):取出灰度图像中i行j列的点。
- image.at(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点,k=[0,1,2],分别代表B,G,R。
实际效果: