**
前言
**
我想再快一点。
在学的起劲的时候,就想催促自己再快一点。但是不可以,短时间学的快、会的少。最后转过头来,发现自己什么都不会。
**
**
颜色空间缩减
**
若图像矩阵元素存储的是单通道像素,使用unsigned char类型的话,像素可有256个不同值。但是如果是三通道图像,这种存储格式的颜色数就太多了,可能对算法性能造成影响。
- 那么,如果只有这些颜色中具有代表性的小部分,应该能达到同样的效果吧。
为了降低运算复杂度,提高算法性能,就需要使用颜色空间缩减。如uchar类型的三通道图像,每个通道取值可以是0~255,于是就有了256X256X256个不同的值,可以定义:
- 0~9的像素值为0
- 10~19的像素值为10
- 20~29的像素值为20
这样的操作将颜色取值降低到25X25X25种情况。而实现这种操作是相当简单的,C++中int类型除法会自动截余。例如:
oldInt=14;
newInt=(oldInt/10)*10=(14/10)*10=1*10=10;
在处理像素时,每个像素都进行一遍这样的计算的话,还是挺麻烦的。但是由于只有256种情况,我们可以先把256种计算好的结果提前保存在表table中。这样每种情况不需要计算,直接从table中取结果即可。
int divideWith=10;
uchar table[256];
for(int i=0;i<256;++i){
table[i]=divideWidth * (i/divideWith);
}
这样就得到table[i]存放值为i的颜色空间缩小的结果。使用时就可以这样:p[ j ] = table[ p[ j ] ] ;
优势是只需读取,无须计算。另外,这里用到除法和乘法这两种费时的运算,应该尽量Yoon加减赋值等运算来替换。
这样简单的颜色空间缩减算法就可以由下面两步组成:
- 遍历图像矩阵每一个像素
- 对像素应用上述公式
**
访问图像中的元素
**
图像元素在内存上的分布之前已经讲了。拿BGR色彩空间举例,图像中的每个元素按Blue、Green、Red三个的顺序组成,一个二维的矩阵每个元素可能是连续存储的------如果空间够大的话-------可以用src.isContinuous()检验。
- 指针访问:
uchar* data = output.ptr<uchar>(i);
- iterator访问:
Mat_<Vec3b>::iterator it = output.begin<Vec3b>();
- 动态地址访问:
output.at<Vec3b>(i, j)[c] = output.at<Vec3b>(i, j)[c] / div * div + div / 2;
下面是个使用三种方法访问像素以实现颜色空间缩减,并没有用表格来提高算法性能,自己试试:
#include<opencv2/opencv.hpp>
#include<iostream>
#include<cmath>
using namespace std;
using namespace cv;
/*最简单*/
void DynAddressColorReduce(Mat& input, Mat& output, int div) {
output = input.clone();//复制创建相同图像
//获取彩色图像像素
for (int i = 0; i < output.rows; ++i) {
for (int j = 0; j < output.cols; ++j) {
for (int c = 0; c < 3; ++c) {
output.at<Vec3b>(i, j)[c] = output.at<Vec3b>(i, j)[c] / div * div + div / 2;
}
}
}
}
//第二种和第三种几乎一样,都是操控内存
void PointerColorRuduce(Mat& input, Mat& output, int div) {
output = input.clone();//复制创建相同的图像
int rowNumber = output.rows;//行数
int colNumber = output.cols * output.channels(); //每一行元素个数
for (int i = 0; i < rowNumber; ++i) {
uchar* data = output.ptr<uchar>(i);//获取第i行的首地址
for (int j = 0; j < colNumber; ++j) {
data[j] = data[j] / div * div + div / 2;
//*data++=*data/div*div+div/2;
}
}
}
/*
isContinuous() : 检测图像是否连续,即是否可以把图像看成一行。通常内存够大的话,图像每一行是连续存放的
*/
void IsContinuousColorReduce(Mat& input, Mat& output, int div) {
output = input.clone();//复制创建相同的图像
int rowNumber = output.rows;
int colNumber = output.cols;
if (input.isContinuous() && output.isContinuous()) {
rowNumber = 1;
colNumber = colNumber * input.rows*input.channels();//一行n列,n为所以像素个数和
}
for (int i = 0; i < rowNumber; ++i) {
const uchar* inData = input.ptr<uchar>(i); //输入数组第i行首地址
uchar* outData = output.ptr<uchar>(i); //输出数组第i行首地址
for (int j = 0; j < colNumber; ++j) {
*outData++ = *inData++ / div * div + div / 2;
}
}
}
/*最稳妥*/
void IteratorColorReduce(Mat& input, Mat& output, int div) {
output = input.clone();//复制创建相同的图像
//获取迭代器
Mat_<Vec3b>::iterator it = output.begin<Vec3b>();//初始位置的迭代器
Mat_<Vec3b>::iterator itend = output.end<Vec3b>();//终止位置的迭代器
//存取彩色图像像素
for (; it != itend; ++it) {
for (int c = 0; c < 3; ++c) {
(*it)[c] = (*it)[c] / div * div + div / 2;
}
}
}
int main() {
Mat src = imread("E:/File/face.jpg");
if (src.empty()) {
cout << "error..." << endl;
return -1;
}
imshow("源图像", src);
Mat dst;
dst.create(src.size(), src.type());
//调用颜色空间缩减函数
IteratorColorReduce(src, dst, 128);
imshow("输出图", dst);
waitKey(0);
}
LUT
Look up table操作,即使用一个原型为operationsOnArrays:LUT()的函数来进行。它用于批量进行图像元素查找、扫描与操作图像:
int divideWith = div;
uchar table[256];
for (int i = 0; i < 256; ++i) {
table[i] = divideWith * (i / divideWith);
}
//首先建立一个Mat型用于查表
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for (int i = 0; i < 256; ++i) {
p[i] = table[i];
}
//调用函数
LUT(input, lookUpTable, output);
速度嘛,比直接用table快7ms左右(有时候)。
计时
- getTickCount()函数返回CPU自某个事件(如启动电脑)以来走过的时钟周期数
- getTickFrequency()函数返回CPU一秒钟所走的时钟周期数。
可以用这两个函数计算某个函数运算时间:
double t1 = static_cast<double>(getTickCount());
//调用颜色空间缩减函数
PointerColorRuduce(src, dst, 128);
double t2 = static_cast<double>(getTickCount());
double interval = (t2 - t1) / getTickFrequency();
cout << "运行时间是 : " << interval * 1000<< " ms \n";
**
图像对比度和亮度调整
**
图像亮度和对比度操作,属于图像处理中相当简单的一种--------点操作算子(point operators)。点操作就是仅仅根据输入像素值(有时加上全局信息或参数),来进行相应的像素值输出。包括:
- 亮度(brightness)调整
- 对比度(contrast)调整
- 颜色校正(color correction)
- 变换(transformations)
常用的点操作是亮度、对比度:
g(x)=af(x)+b*
- f(x)表示源图像像素
- g(x)表示输出图像像素
- a(a>0)被称为增益(gain),常用来控制图像对比度
- b通常被称为偏置(bias),常用来控制图像亮度
在访问图片中的像素时,经常写作
g(i,j)=af(i,j)+b*
for(int y=0;y<image.rows;++i){
for(int x=0;x<image.cols;++x){
for(int c=0;c<3;++c){
newImg.at<Vec3b>(y,x)[c] = saturate_cast<uchar>(
g_ContrastValue * 0.01 * image.at<Vec3b>(y,x)[c] + g_BrightValue
);
}
}
}
- 上面之所以乘以0.01是因为,参数g_Contrast最大300,而对比度一般取值0.0~3.0f。
下面是一个图像亮度、对比度值调整的示例:
#include<opencv2/opencv.hpp>
#include<iostream>
#define WINDOW_NAME1 "【源图像】"
#define WINDOW_NAME2 "【效果图】"
using namespace std;
using namespace cv;
int g_ContrastValue;
int g_BrightValue;
Mat g_Src;
Mat g_Dst;
static void on_ContrastAndBright(int, void*) {
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
for (int y = 0; y < g_Src.rows; ++y) {
for (int x = 0; x < g_Src.cols; ++x) {
for (int c = 0; c < 3; ++c) {
g_Dst.at<Vec3b>(y, x)[c] = saturate_cast<uchar>(
g_ContrastValue*0.01 * g_Src.at<Vec3b>(y, x)[c] + g_BrightValue
);
}
}
}
imshow(WINDOW_NAME1, g_Src);
imshow(WINDOW_NAME2, g_Dst);
}
int main() {
g_Src = imread("E:/File/face.jpg");
if (!g_Src.data) {
cout << "error";
system("pause");
}
g_Dst = Mat::zeros(g_Src.size(), g_Src.type());
g_ContrastValue = 80;
g_BrightValue = 80;
namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
createTrackbar("对比度:", WINDOW_NAME2, &g_ContrastValue, 300, on_ContrastAndBright);
createTrackbar("亮度:", WINDOW_NAME2, &g_BrightValue, 300, on_ContrastAndBright);
on_ContrastAndBright(0, 0);
while (char(waitKey(10)) != 'q') {
}
destroyAllWindows();
}
参考:《Opencv3编程入门》