Opencv像素处理与访问
对于图像处理来说,如果我们了解处理图像的具体算法,那么我们就可以通过直接操作图片的像素点来实现这些算法。所以本文就总结一下Opencv像素处理和访问的一些知识。
文章目录
1. 色彩空间缩减
1.1色彩空间缩减的必要性
图像的本质是矩阵,是由一个个的像素点组成的。储存图片数据往往会占用巨大的空间。假如的单通道的黑白图片,每个像素点用0-255这256个数字来表示颜色。而如果是三通道的彩色图片,则每个像素点就有256x256x256种可能的颜色,这无疑对于数据储存来说是笔巨大的开销。
如果对色彩空间进行缩减,可以有效的减少数据储存压力。我们可以通过对颜色进行近似表示的方法,缩减色彩空间。比如对于一个通道256种颜色:0-10表示为0,11-20表示为1…以此类推,那么我们就仅需要26个数字来表示一个通道的颜色,对于三通道彩色图来说,这个储存空间压缩比例就更加客观了,256x256x256减少到了26x26x26。
如何实现这种颜色近似表示呢?在c++中,int类型除法具有截断小数点位数的功能,就可以通过这种方法,处理色彩空间,公式如下
I
n
e
w
=
I
o
l
d
10
∗
10
Inew=\frac{Iold}{10}*10
Inew=10Iold∗10
举例:
Iold = 23,则Inew=23/10*10=2*10=20
1.2 查找表方法缩减色彩空间
但是如果所有的像素点都要做一次近似计算,也是一种巨大的运算量。所以,我们可以利用查表的思想简化这一过程。我们可以创建一张记录了0-256分别对应变换以后哪个数字的表,这样就不需要每次都计算了,而是查找变换表即可。
//查找表的创建
uchar table[256];
int divideWidth = 100;
for (int i = 0; i < 256; i++)
{
table[i] = i / divideWidth*divideWidth;
}
然后假设图片中每一个像素点为p[i],则通过下列变换即可实现色彩空间缩减
p[i]=table[p[i]];
1.3 LUT函数
Opencv中内置了一个查找表函数LUT,用来做色彩空间压缩。其函数定义为:
void LUT(InputArray src, InputArray lut, OutputArray dst);
第一个参数:输入图像
第二个参数:查找表
第三个参数:输出图片
这三个参数的类型均为Mat类型
使用例子
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
//创建数值表
uchar table[256];
int dividedValue = 100;
for (int i = 0; i < 256; i++)
{
table[i] = i / dividedValue * dividedValue;
}
//定义图片
Mat srcImg, lut, dstImg;
srcImg = imread("1.png");
//创建查找表
lut.create(1, 256, CV_8UC1); //查找表一个通道就可以,处理三通道图片的时候,会把三个通道都安装lut的规则进行变换
uchar* p = lut.data;
for (int i = 0; i < 256; i++)
{
p[i] = table[i];
}
//查找表函数
imshow("a", srcImg);
LUT(srcImg, lut, dstImg);
imshow("b", dstImg);
waitKey(0);
}
2. 判断程序的运行效率
处理图像就必须了解图像处理算法的运行效率,这个可以通过程序的运行时间判断,有这样两个函数:
double getTickFrequency();//指示1秒钟CPU能够运行多少个周期
int64 getTickCount(); //用来判断cpu当前的运行周期
通过这两个函数我们就能够判断一个程序的运行时间,举例如下:
double t1=(double)getTickCount();
//do something
t1 = ((double)getTickCount()-t1)/getTickFrequency();
cout << t1 << endl;
3. 访问图像中像素的方法
3.1 指针
通过指针访问像素的特点是:速度最快,但是指针本身就具有不安全的特性。
写法如下:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
void colorReduce(Mat& srcImg, Mat& dstImg, int dividedValue)
{
//创建查找表
char table[256];
for (int i = 0; i < 256; i++)
{
table[i] = i / dividedValue * dividedValue;
}
//记录图片数据
int row = srcImg.rows;
int col = srcImg.cols;
int channel = srcImg.channels();
srcImg.copyTo(dstImg);
//使用指针的方法访问像素
for (int i = 0; i < row; i++)
{
uchar* p = dstImg.ptr<uchar>(i); //ptr是mat类的一个模板函数,返回值是第i行的头指针,尖括号里放的是返回值类型
for (int j = 0; j < col * channel; j++)
{
p[j] = table[p[j]];
}
}
}
int main()
{
Mat srcImg, dstImg;
srcImg = imread("1.png");
srcImg.copyTo(dstImg);
imshow("a", dstImg);
colorReduce(srcImg, dstImg, 100);
imshow("b", dstImg);
waitKey(0);
}
3.2 迭代器
迭代器访问像素的特点是:只需要知道图片第一个像素的地址和最后一个像素的地址就可以遍历像素了。操作非常简单,而且不存在内存越位的问题,因此非常的安全。
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
void colorReduce(Mat& srcImg, Mat& dstImg, int dividedValue)
{
//创建查找表
char table[256];
for (int i = 0; i < 256; i++)
{
table[i] = i / dividedValue * dividedValue;
}
//记录图片数据
int row = srcImg.rows;
int col = srcImg.cols;
int channel = srcImg.channels();
srcImg.copyTo(dstImg);
switch (channel)
{
//处理单通道的灰色图
case 1:
{
MatIterator_<uchar> it, end;//创建迭代器
for (it = dstImg.begin<uchar>(), end = dstImg.end<uchar>(); it != end; it++)
{
(*it) = table[(*it)];
}
break;
}
//处理三通道的彩色图
case 3:
{
cv::Mat_<cv::Vec3b>::iterator it = dstImg.begin<Vec3b>();//创建迭代器
cv::Mat_<cv::Vec3b>::iterator end = dstImg.end<Vec3b>();
for (; it != end; it++)
{
(*it)[0] = table[(*it)[0]];
(*it)[1] = table[(*it)[1]];
(*it)[2] = table[(*it)[2]];
}
break;
}
}
}
int main()
{
Mat srcImg, dstImg;
srcImg = imread("1.png");
srcImg.copyTo(dstImg);
imshow("a", dstImg);
colorReduce(srcImg, dstImg, 100);
imshow("b", dstImg);
waitKey(0);
}
这里要注意一个问题,当case语句里面定义变量的时候,一定要加{},否则会报错
3.3 动态地址
动态内存使用Mat类的at方法进行,特点是:直观,能够非常方便的看出行列值
写法如下:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
void colorReduce(Mat& srcImg, Mat& dstImg, int dividedValue)
{
//创建查找表
char table[256];
for (int i = 0; i < 256; i++)
{
table[i] = i / dividedValue * dividedValue;
}
//记录图片数据
int row = srcImg.rows;
int col = srcImg.cols;
int channel = srcImg.channels();
srcImg.copyTo(dstImg);
switch (channel)
{
//处理单通道的灰色图
case 1:
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
dstImg.at<uchar>(i, j) = table[dstImg.at<uchar>(i, j)];
}
}
break;
}
//处理三通道的彩色图
case 3:
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
dstImg.at<Vec3b>(i, j)[0] = table[dstImg.at<Vec3b>(i, j)[0]];
dstImg.at<Vec3b>(i, j)[1] = table[dstImg.at<Vec3b>(i, j)[1]];
dstImg.at<Vec3b>(i, j)[2] = table[dstImg.at<Vec3b>(i, j)[2]];
}
}
break;
}
}
}
int main()
{
Mat srcImg, dstImg;
srcImg = imread("1.png");
srcImg.copyTo(dstImg);
imshow("a", dstImg);
colorReduce(srcImg, dstImg, 100);
imshow("b", dstImg);
waitKey(0);
}
参考文献
[1] OpenCV学习笔记二(scan images)https://blog.csdn.net/jameshater/article/details/50756729
[2] 再谈OpenCV中查询表lookup table的LUT函数https://blog.csdn.net/jameshater/article/details/50759650?utm_source=blogxgwz8