Opencv学习六访问图像中的元素和调整图像亮度对比度

**

前言

**
我想再快一点。

在学的起劲的时候,就想催促自己再快一点。但是不可以,短时间学的快、会的少。最后转过头来,发现自己什么都不会。

**

**

颜色空间缩减

**
若图像矩阵元素存储的是单通道像素,使用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加减赋值等运算来替换。

这样简单的颜色空间缩减算法就可以由下面两步组成:

  1. 遍历图像矩阵每一个像素
  2. 对像素应用上述公式

**

访问图像中的元素

**

图像元素在内存上的分布之前已经讲了。拿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编程入门》

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/86573891