Opencv改变图像亮度和对比度以及优化

https://blog.csdn.net/u013139259/article/details/52145377

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013139259/article/details/52145377
问题介绍
理论介绍
代码介绍
优化方式与实验结果
问题介绍

图像处理中改变亮度和对比度是很基础的需求,算法也相对简单。博主的工作是要模拟待处理视频的不同光照下的效果。博主想到的方式就是
利用Opencv读取视频的每一帧,然后进行处理成不同亮度和对比度的图像,然后在写进去。

理论介绍

Opencv提供了强大的图像处理算法接口,关于改变图像的亮度和对比度,也提供了官方的教程。链接如下。

官方介绍

一般这类工作叫做图像处理算子,或者图像变换,图像变换分为两种。

点算子(像素变换)
领域算子(基于区域的)
其中改变图像的亮度和对比度是通过像素交换实现的,属于点算子。
至于,怎么改变原图的单个像素点。公式如下。

代码介绍

cv::Mat new_Mat = cv::Mat::zeros( from_mat.size(), from_mat.type());
for( int y = 0; y < from_mat.rows; y++)
{
for( int x = 0; x < from_mat.cols; x++ )
{
for(int c = 0; c < 3; c++)
{
new_Mat.at<cv::Vec3b>(y , x)[c] = cv::saturate_cast<uchar>(ip.alpha*(from_mat.at<cv::Vec3b>(y , x)[c]) + ip.beta);
}
}
}
return new_Mat ;
1
2
3
4
5
6
7
8
9
10
11
12
注意1:为了访问图像的每一个像素,我们使用这一语法: new_Mat.at(y,x)[c] 其中, y 是像素所在的行, x 是像素所在的列, c 是R、G、B(0、1、2)之一。
因为 运算结果可能超出像素取值范围,还可能是非整数(如果 alpha 是浮点数的话),所以我们要用 saturate_cast 对结果进行转换,以确保它为有效值。
优化方式与实验结果

通过以上的方式,在进行对每一帧的视频处理的时间(视频帧的大小是1920x1080),大概是3s左右,很明显,这太慢了。所以,实验了几个其他的方式,来进行算法优化。算法的优化思路如下。

原算法慢很明显是因为循环次数过大,因为有3层循环。所以,通过改变mat帧数据的遍历方式,逐渐减少循环层数。

优化方式一

对于一个像素点,它的RGB的数据存储是连续的,所以,有以下公式。

每一行的数据点(uchar数据) = 每一行的像素点 X 通道数。

而且,mat的帧使用指针的方式进行遍历。

代码如下。

cv::Mat new_Mat = cv::Mat::zeros( from_mat.size(), from_mat.type());

int nl = from_mat.rows ;
int nc = from_mat.cols*from_mat.channels();

/*传入参数行数nl*/

for(int j= 0 ; j < nl ; j++)
{
uchar * data = from_mat.ptr<uchar>(j);

uchar * output = new_Mat.ptr<uchar>(j);

for(int i = 0 ; i < nc ; i++)
{
output[i] = cv::saturate_cast<uchar>(data[i] + ip.beta);
}

}

return new_Mat;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
算法名称 一帧图像处理时间
算法优化第一步 0.148778s
优化方式二
每一行的数据都是连续存储的,但是不同行之间就不一定了。以为有时处于效率的原因,每行都会填补一些额外的像素。主要是当行的长度是4或者8的倍数,一些多媒体处理芯片可以更高效地处理像素,这些额外的像素不会被显示或者保存,填补的值会被忽略。 Opencv提供了接口(isContinuous)判断是否图像进行了内存填补。如果函数返回为true的话,那么代表图像没有进行填补,因此我们可以通过利用存储的连续性,在一个循环中进行mat数据遍历。

int nl = from_mat.rows ;
int nc = from_mat.cols*from_mat.channels();

if(from_mat.isContinuous())
{
nc = nc*nl;
nl = 1 ;

uchar * data = from_mat.ptr<uchar>(0);
for(int i = 0 ; i < nc ; i++)
{

data[i] = cv::saturate_cast<uchar>(data[i] + ip.beta);
}

}

return from_mat;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
算法名称 一帧图像处理时间
算法优化第二步 0.149215s
从时间上来说,这种方式并没有加速,所以,相对第一种方式,真正能做到优化的原因,可能是和改成指针遍历有关。

优化方式三
从代码中可以看出主要的耗时操作是二维的加法运算。那么因此我们可以通过利用多核CPU来进行并行加速。本文利用的并行加速的第三方库,是TBB。关于TBB和使用方式,本文不做介绍。主要是使用了其中的
parallel_for函数。

int nl = from_mat.rows ;
int nc = from_mat.cols*from_mat.channels();

if(from_mat.isContinuous())
{
nc = nc*nl;
nl = 1 ;

uchar * data = from_mat.ptr<uchar>(0);
parallel_for(blocked_range<size_t>(0 , nc) , AddClass(data , ip.beta));

}

return from_mat;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
算法名称 一帧图像处理时间
算法优化第三步 0.074073s
利用TBB进行并行加速第二种方式,时间又缩小了一般,可见并发处理的有效性。当然,TBB的学习资料并不是很多,这个也不是很容易使用的。
还有就是TBB并不定对所有算法加速,也是和程序,运行环境相关的,使用不当会适得其反。

直接调用Opencv库函数

Opencv提供了convertTo() 可以进行相同的操作。

cv::Mat new_Mat =cv::Mat::zeros( from_mat.size(), from_mat.type());
from_mat.convertTo(new_Mat , -1 , ip.alpha , ip.beta);
return new_Mat ;
1
2
3
算法名称 一帧图像处理时间
算法优化第四步 0.015003s
处理一帧又快了3倍。本人不知道Opencv该函数的实现,所以并不能解释为什么这么快。但是你可以去下载Opencv的源码,查看该方法的具体实现方式。

总结

Opencv也集成了TBB和cuda,但是默认的并没有编译到源码中,要利用TBB或者cuda加速的方式,可以通过cmake进行重新编译其中,当然,利用TBB并发或者cuda利用GPU加速并不定能理想,具体问题具体分析。而且,利用cuda,需要有NVIDIA的显卡硬件支持的。
---------------------
作者:张骞晖2
来源:CSDN
原文:https://blog.csdn.net/u013139259/article/details/52145377
版权声明:本文为博主原创文章,转载请附上博文链接!

原文地址:https://www.cnblogs.com/jukan/p/10096706.html