【OpenCV C++20 学习笔记】调节图片对比度和亮度(像素变换)

调节图片对比度和亮度(像素变换)

原理

关于OpenCV的配置和基础用法,请参阅本专栏的其他文章:垚武田的OpenCV合集

以下的原理来自Richard Szeliski的书《Computer Vision: Algorithms and Applications》(《计算机视觉:算法和应用》)。

像素变换

图片处理的操作基本上就是一个传入一张或多张图片,然后输出一张结果图片的方法。
对某个图片对象进行的操作可以分为以下两大类:

  • 点操作:像素变换
  • 域操作:涉及到相邻的像素

这章主要讨论像素变换。在像素变换中,每个像素的计算结果只与输入的像素和其他参数有关,不与图片中的其他像素相关。像素变换的应用包括图片亮度、对比度调整,以及颜色校正和颜色变换等。

亮度和对比度调整

在亮度和对比度的线性调整中,像素变换的算法非常简单,就是一个简单的线性变换:
g ( x ) = α f ( x ) + β g(x) = \alpha f(x) + \beta g(x)=αf(x)+β

  • α > 0 \alpha > 0 α>0,为增强参数; β \beta β为偏移参数
  • α \alpha α控制对比度; β \beta β用来控制亮度
  • f ( x ) f(x) f(x)为转换前的像素, g ( x ) g(x) g(x)为转换后的像素

也可以用行列坐标的形式来表示像素:
g ( i , j ) = α f ( i , j ) + β g(i, j) = \alpha f(i ,j) + \beta g(i,j)=αf(i,j)+β

  • i i i j j j分别代表行号和列号

代码实现

首先导入图片并储存到Mat对象中。

//CommandLineParser对main函数输入的参数进行解析,最后的字符串代表以下意义:
//@input表示一个有顺序的参数,将其命名为input
//lena.jpg,代表input的默认值
//input image,是对input参数的解释,说明它是输入的图像
CommandLineParser parser(argc, argv, "{@input | lena.jpg | input image}");
Mat image{ imread(parser.get<String>("@input")) };	//获取参数解析中的input参数
if (image.empty()) {//如果打开失败,则输出错误信息,并退出程序cout << "无法打开图片!\n" << endl;cout << "输入图片:" << argv[0] << "<参数错误>" << endl;return -1;
}

接着,创建一个新的Mat对象来储存变换后的结果。这个新对象的所有值初始化为0,而且具有和原图像同样的大小和类型:

Mat new_image{ Mat::zeros(image.size(), image.type()) };

Mat对象的创建方法可以参阅专栏中的《【OpenCV C++20 学习笔记】基本图像容器——Mat》

然后,声明 α \alpha α β \beta β这两个参数,并让用户能够通过控制台输入它们的值:

double alpha{ 1.0 };	//对比度控制参数
int beta{ 0 };			//亮度控制参数cout << "基础线性变换" << endl;
cout << "-----------" << endl;
cout << "* 输入alpha值 [1.0-3.0]:"; cin >> alpha;
cout << "* 输入beta值 [0-100]:"; cin >> beta;

现在,用一个嵌套的for循环语句,遍历原图片中的每一个像素,并对每一个像素都进行变换操作:

for (int y{ 0 }; y < image.rows; y++) {	//遍历行for (int x{ 0 }; x < image.cols; x++) {	//遍历列for (int c{ 0 }; c < image.channels(); c++) {	//遍历颜色通道new_image.at<Vec3b>(y, x)[c] =saturate_cast<uchar>(alpha * image.at<Vec3b>(y, x)[c] + beta);}}
}
  • 因为前面读取图片的时候,我们使用的是默认的BGR3通道格式。所以对于矩阵中的每一个数据项,我们用Vec3b数据类型来接收,并用下标c对3个通道中的每个通道值进行访问,最终每个数值的访问都使用了y(行数)、x(列数)、c(通道数);
  • 因为线性变换的计算可能使得结果超出原有类型的值域,或者变成其他类型(比如,当alpha为浮点数时,计算结果就会自动转换成浮点数)。所以,必须使用saturate_cast对最终结果进行类型转换。
    最后,创建窗口分别展示原始图片和变换后的图片
imshow("原始图片", image);
imshow("新图片", new_image);waitKey(0);

更简便的方法

除了使用for循环对矩阵中的所有值进行遍历和转换之外,还可以使用更加便利的转换方法:

image.convertTo(new_image, -1, alpha, beta);

正如我在《【OpenCV C++20 学习笔记】操作图片》一文中详细描述的那样,convertTo函数实际上就是在执行一个线性变化的操作。其函数原型如下:
void cv::Mat::convertTo(OutputArray m, int rtype, double alpha = 1, double beta = 0) const
其算法如下:
m ( x , y ) = s a t u r a t e _ c a s t < r T y p e > ( α ( ∗ t h i s ) ( x , y ) + β ) m(x,y) = saturate\_cast< rType>(\alpha(*this)(x, y)+\beta) m(x,y)=saturate_cast<rType>(α(this)(x,y)+β)
实质上就等于线性变化+类型转换的操作,即上一节代码中for循环体内的操作。所以上一节代码中的整个for循环,可以用convertTo函数代替。
上一节的代码只是为了展示像素变换的原理,在实际应用中还是建议使用convertTo()函数直接进行变换。

结果展示

使用2.2的 α \alpha α值和50的 β \beta β
参数输入

结果如下:
像素变换结果

γ \gamma γ校正及其实操案例

在这个案例中将运用另外一种亮度调整方法—— γ \gamma γ校正,来修复一张低曝光的照片。

线性变换的缺点

在上述线性变换的例子中,亮度的调整是通过给每个像素值加上或减去一个常量,即偏移参数 β \beta β。如果调整后的结果超出了值域,则会用saturate_cast进行类型转换,使其仍然落在值域之中。

saturate_cast的具体原理,请参阅本专栏中的《【OpenCV C++20学习笔记】矩阵上的掩码(mask)操作》中的“类型转换”小节

下面的直方图展示了偏移参数为80时,像素分布的改变:
亮度调整示意图

  • 灰色部分为图像的原始像素分布
  • 黑色部分为调整后的像素分布
  • 横坐标为每个颜色值
  • 纵坐标为每个颜色值对应的像素个数

可以看到颜色值整体往右偏移了,而且最大值和最小值上的像素个数显著增加,这是值域调整的结果。
另一方面,对比度的调整在上例中是通过改变 α \alpha α值实现的。 α \alpha α越大,对比度越高;反之,对比度越低。下面的直方图展示了,当 α \alpha α值小于1的时候,像素分布的改变如下:
对比度调整示意图

  • 图例与上图相同

与上图对比,这里的黑色部分像被横向挤压了,颜色值的值域变窄了,像素分布也更加集中了。
通过这两张图我们也可以看到线性变换的一些缺点:

  • 由于saturate_cast的值域控制,会丢失一些图片的信息,即原始值域会被截断,导致变换后的颜色值值域变窄
  • 亮度的调整同时会影响图片的对比度,如第一张图中所示, β \beta β参数在偏移像素分布的同时,也使像素更加集中
  • 变换后颜色值最大值和最小值处的像素分布会激增,会导致图片过曝

γ \gamma γ校正

γ \gamma γ校正使用非线性变换来调整图片的亮度,其原理如下:
O = ( I 255 ) γ × 255 O= (\frac{I}{255})^\gamma \times 255 O=(255I)γ×255

  • I I I为像素的原值颜色值
  • O O O为像素变换后的颜色值
  • γ \gamma γ为变换系数

变换结果 O O O和原始值 I I I之间由于是非线性的关系,所以并不是每个像素的变换效果都是一样的。下图显示在不同的 γ \gamma γ值下, O O O I I I之间的关系:
非线性变化

  • 横坐标为原始值I
  • 纵坐标为变换值O

可以看到,当 γ < 1 \gamma<1 γ<1的时候,原始的最小值(即I=0)的增加更多;反之,当 γ > 1 \gamma>1 γ>1时,原始的最小值增加更少。

低曝光图片矫正案例

下面两张图,左边是原图,右边是用线性变换矫正后的图片( α = 1.3 \alpha=1.3 α=1.3 β = 40 \beta=40 β=40):
线性变换案例
图片的整体亮度被调高了,但是很明显,天空的细节也丢失了,显得有点过曝。这就是上面所说的saturate_cast值域控制的结果。


下面是 γ \gamma γ校正( γ = 0.4 \gamma=0.4 γ=0.4)的结果:
非线性变换案例
效果高下立判!

原图、线性变换和 γ \gamma γ校正的像素分布直方图如下:
校正的像素分布直方图

  • 左图:线性变换后
  • 中图:原始图片
  • 右图: γ \gamma γ校正后
  • 3幅图的y轴并不一致

可以看到,在原图中,左边的像素偏多,也就是颜色值低(暗部)的像素偏多。在线性矫正之后,即左图中,可以看到最右边有个到顶的颜色值,这就是值域控制后的最大颜色值的像素分布(saturate_cast将所有超出最大值的变换结果都变成了最大值)。但是在 γ \gamma γ校正之后,即右图中,可以看到相对于原图往右偏移了,同时,暗部和亮部也发生了分布的改变。但是显然,暗部的变化更多(数量减少,且更分散),亮部的变化偏少。这就防止了图片的过曝。下图标注了对比的结果:
校正对比
所以可以得出以下结论:
相对于线性变换, γ \gamma γ校正在调整图片亮度上效果更好,也更能保留原始图片的细节

代码实现

在OpenCV中可以用LUT函数实现 γ \gamma γ校正。
其逻辑就是:用非线性算法计算出所有颜色值变换后的值,储存到一个查询表中;然后,用查询表的值一一替换原始图片中对应的颜色值。

double gamma_{ 0.4 };	//确定gamma值
Mat lookUpTable(1, 256, CV_8U);	//新建查询表
uchar* p = lookUpTable.ptr();	//获取查询表的指针,方便后面填充值
for (int i{ 0 }; i < 256; ++i)	//填充查询表p[i] = saturate_cast<uchar>(pow(i / 255.0, gamma_) * 255.0);	//非线性转换算法Mat res = image.clone();	//复制原始图片对象,作为储存变换结果的对象
LUT(image, lookUpTable, res);	//按查询表中的值,替换原始图片中的值

使用查询表能够提高替换原图中所有颜色值的速度。

查询表原理及LUT函数的用法,可以参阅本专栏中的【OpenCV C++20 学习笔记】扫描图片数据一文。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3266319.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

视频号矩阵系统,AI自动生成文案,实现批量上传视频和定时发布

在数字化浪潮席卷全球的今天&#xff0c;视频内容已成为信息传播的重要载体。然而&#xff0c;对于众多自媒体创作者和企业而言&#xff0c;如何高效、精准地发布视频内容&#xff0c;依然是一个不小的挑战。幸运的是&#xff0c;随着技术的不断进步&#xff0c;视频号矩阵系统…

0725,进程间传递文件描述符,socketpair + sendmsg/recvmsg

我要碎掉了我要碎掉了我要碎掉了 上课喵&#xff1a; pipe匿名管道的问题 #include <func.h>int main() {int fds[2];pipe(fds);pid_t pidfork();if(pid>0){ //fatherclose(fds[0]);//close readint fdopen("file2.txt",O_RDONLY);printf("father: …

Idea2024 创建Meaven项目没有src文件夹

1、直接创建 新建maven项目&#xff0c;发现没有src/main/java 直接新建文件夹&#xff1a;右击项目名->new->Directory 可以看到idea给出了快捷创建文件夹的选项&#xff0c;可以根据需要创建&#xff0c;这里点击src/main/java 回车&#xff0c;可以看到文件夹已经创建…

用户管理与高级SQL语句(数据库管理与高可用)

1.表&#xff08;Table &#xff09; 数据库中的表与我们日常生活中使用的表格类似&#xff0c;它也是由行&#xff08;Row&#xff09; 和列&#xff08;Column&#xff09;组成的。列由同类的信息组成&#xff0c;每列又称为一个字段&#xff0c;每列的标题称为字段名。行包…

sql注入的专项练习 sqlilabs(含代码审计)

在做题之前先复习了数据库的增删改查&#xff0c;然后自己用本地的环境&#xff0c;在自己建的库里面进行了sql语句的测试&#xff0c;主要是回顾了一下sql注入联合注入查询的语句和sql注入的一般做题步骤。 1.获取当前数据库 2.获取数据库中的表 3.获取表中的字段名 一、sql…

51单片机嵌入式开发:19、STC89C52R控制LCD1602码表+数码管+后台数显(串口)

STC89C52R控制LCD1602码表数码管后台数显&#xff08;串口&#xff09; 1 概述1.1 项目概述1.2 项目组成部分1.3 功能描述 2 开发环境2.1 支持设备2.2 硬件电路 3 软件代码工程4 演示4.1 Proteus仿真4.2 实物演示 5 总结 1 概述 1.1 项目概述 本项目旨在利用STC89C52R单片机实…

联通智慧商业零售解决方案,旨在为全球零售企业提供低成本、高效能的组网与通信服务

联通智慧商业零售解决方案&#xff1a;驱动零售业全球布局与创新 在全球化的大背景下&#xff0c;零售业面临着前所未有的机遇与挑战。随着消费者需求的多样化和市场环境的快速变化&#xff0c;零售商必须不断寻求创新&#xff0c;以保持竞争力。中国联通国际&#xff0c;凭借…

优略解距离法—Topsis模型【清风数模学习笔记】

层次分析法的局限性 &#xff08;1&#xff09;决策层不能太多 &#xff08;2&#xff09;数据已知&#xff0c;使用层次分析法不准确 构造计算评分 相较于取卷面理论上的最高分&#xff08;100&#xff09;和最低分&#xff08;0&#xff09;&#xff0c;取分数区间上的最…

Golang | Leetcode Golang题解之第290题单词规律

题目&#xff1a; 题解&#xff1a; func wordPattern(pattern string, s string) bool {word2ch : map[string]byte{}ch2word : map[byte]string{}words : strings.Split(s, " ")if len(pattern) ! len(words) {return false}for i, word : range words {ch : patt…

CSS技巧专栏:一日一例 12 -纯CSS实现边框上下交错的按钮特效

CSS技巧专栏&#xff1a;一日一例 12 -纯CSS实现边框上下交错的按钮特效 大家好&#xff0c;今天我们来做一个上下边框交错闪动的按钮特效。 本例图片 案例分析 虽说这按钮给人的感觉就是上下两个边框交错变换了位置&#xff0c;但我们都知道border是没法移动的。那么这个按…

python实现接缝雕刻算法

python实现接缝雕刻算法 接缝雕刻算法步骤详解Python实现详细解释优缺点应用领域接缝雕刻算法(Seam Carving Algorithm)是一种内容感知的图像缩放技术,可以智能地改变图像的尺寸,而不会明显影响其重要内容。它通过动态规划的方式寻找图像中的“接缝”,即在图像中从上到下或…

排序系列 之 希尔排序

&#xff01;&#xff01;&#xff01;排序仅针对于数组哦本次排序是按照升序来的哦 介绍 英文名为ShellSort&#xff0c;又称“缩小增量排序”是直接插入排序算法的一种更高效的改进版本希尔排序是把记录按下标的指定步长分组&#xff0c;然后按照每组使用直接插入排序&#…

【全面讲解下Docker in Docker的原理与实践】

🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 👉目录 👉前言👉原理👉实践👉安全和最佳实践👉前言 🦛…

Python 教程(三):字符串特性大全

目录 专栏列表前言1. 字符串基础2. 字符串方法字符串查询字符串修改字符串切片 3. 字符串格式化旧式格式化&#xff08;% 操作符&#xff09;str.format() 方法f-string&#xff08;Python 3.6&#xff09; 4. 字符串编码5. Unicode 和 ASCII6. 正则表达式7. 字符串比较8. 字符…

从零入手人工智能(6)—— 聚类

1.小故事 有一家零售连锁店&#xff0c;他们以其精准的市场定位和个性化的顾客服务而闻名&#xff0c;随着市场竞争的加剧和顾客需求的多样化&#xff0c;他们的管理层开始意识到&#xff0c;只有更加深入地了解他们的顾客群体&#xff0c;以便更好地满足他们的需求。 他们定…

文件包含漏洞--pyload

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.PHP伪协议利用 php://协议 php://filter &#xff1a;用于在读取和写入文件时进行过滤和转换操作。 通常利用文件包含执行php://filter伪协议读取的源码会被服务器执行从而不会显示&#xf…

重塑生态体系 深挖应用场景 萤石诠释AI时代智慧生活新图景

7月24日&#xff0c;“智动新生&#xff0c;尽在掌控”2024萤石夏季新品发布会在杭州举办。来自全国各地的萤石合作伙伴、行业从业者及相关媒体&#xff0c;共聚杭州&#xff0c;共同见证拥抱AI的萤石&#xff0c;将如何全新升级&#xff0c;AI加持下的智慧生活又有何不同。 发…

MATLAB学习日志DAY16

16.数组类型&#xff08;1&#xff09; 16.1多维数组 MATLAB 环境中的多维数组是具有多个下标的数组。创建多维数组的一种方法是调用具有多个参数的 zeros、ones、rand 或 randn。 R randn(3,4,5); 创建一个 345 数组&#xff0c;共包含 3*4*5 60 个正态分布的随机元素。…

【Golang 面试基础题】每日 5 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

升腾c92刷bios(一)

一、刷机bios 原机的bios是不允许设备通过usb接口进行系统更换&#xff0c;需要输入boot的正好和密码才可以&#xff0c;可惜的是我们并不知道原机的密码是什么。 步骤如下&#xff1a; 1、u盘进行格式化为fat32格式 2、将c92的bios程序进行备份和升级&#xff08;文章结尾提…