【OpenCV C++20 学习笔记】调节图片对比度和亮度(像素变换)
调节图片对比度和亮度(像素变换)
- 原理
- 像素变换
- 亮度和对比度调整
- 代码实现
- 更简便的方法
- 结果展示
- γ \gamma γ校正及其实操案例
- 线性变换的缺点
- γ \gamma γ校正
- 低曝光图片矫正案例
- 代码实现
原理
关于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 学习笔记】扫描图片数据一文。
相关文章:

【OpenCV C++20 学习笔记】调节图片对比度和亮度(像素变换)
调节图片对比度和亮度(像素变换) 原理像素变换亮度和对比度调整 代码实现更简便的方法结果展示 γ \gamma γ校正及其实操案例线性变换的缺点 γ \gamma γ校正低曝光图片矫正案例代码实现 原理 关于OpenCV的配置和基础用法,请参阅本专栏的其…...
web UI自动化测试 浏览器模式设置
自动化之浏览器模式设置 做selenium UI自动化测试时,每次都需要启动浏览器、用例运行结束后再关闭浏览器,浏览器启动相当地耗费时间,在本机运行用例的话还得放开双手,可以使用chrome的headless模式,让浏览器在后台运行…...

OpenCV图像滤波(1)双边滤波函数bilateralFilter的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 功能描述 bilateralFilter是图像处理和计算机视觉领域中的一种高级图像滤波技术,特别设计用于在去除噪声的同时保留图像的边缘和细节。相比于传…...
前端开发使用Big.js精算避免误差
1、下载 npm install big.js 全局引入还是局部引入可根据项目框架及个人需求 2、静态引入 < script src https://unpkg.com/big.js6.0.0/big.mjs > </ script > 或者 import Big from https://raw.githubusercontent.com/mikemcl/big.js/v6.0.0/big.mjs; i…...
在 Ubuntu 22.04/20.04 安装 CVAT 和 SAM 指南
1. 安装 Docker 和 Docker Compose sudo apt-get update sudo apt-get --no-install-recommends install -y \apt-transport-https \ca-certificates \curl \gnupg-agent \software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-ke…...

【SpringCloud】 微服务分布式环境下的事务问题,seata大合集
目录 微服务分布式环境下的事务问题 分布式事务 本地事务 BASE理论与强弱一致性 BASE理论 强弱一致性 常见分布式事务解决方案 - 2PC 常见分布式事务解决方案 - TCC 常见分布式事务解决方案 - 最大努力通知 常见分布式事务解决方案 - 最终一致性 Seata介绍与术语 Seata…...

vite5+vue3开发阅读APP实战笔记20240725
目前界面长成这样: 配置别名 修改vite.config.js import {defineConfig} from vite import vue from vitejs/plugin-vue import path from "path"// https://vitejs.dev/config/ export default defineConfig({server: {open: true,port: 8088,},plug…...

Intel任命Micron技术开发主管领导Intel Foundry制造运营
- **新闻要点**:Intel聘请了Micron的技术开发主管Dr. Naga Chandrasekaran担任首席全球运营官、执行副总裁以及Intel Foundry制造和供应链组织的总经理。他将负责Intel的所有制造运营事务。 #### 任命背景 - **领导团队**:Chandrasekaran将成为Intel执行…...

苹果发布iOS 18 Beta 4,新增CarPlay 壁纸等多项功能改进
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 iOS 18 Beta 4:新功能与改进的探索 苹果公司在2024年7月9日向开发者推送了iOS 18的第四个开发者预览版Beta 4更新,内部…...

谷粒商城实战笔记-50-51-商品分类的删除
文章目录 一,50-商品服务-API-三级分类-删除-逻辑删除1,逻辑删除的配置1.1 配置全局的逻辑删除规则(可省略)1.2 配置逻辑删除Bean(可省略)1.3 Bean相应字段上加上注解TableLogic 2,后台接口开发…...

vue3+g2plot实现词云图
词云图 效果预览: 核心代码: import {WordCloud } from @antv/g2plot;fetch(https://gw.alipayobjects.com/os/antfincdn/jPKbal7r9r/mock.json).then((res) => res.json()).then((data) => {const wordCloud = new WordCloud(container, {data,wordField: x,weigh…...

Golang | Leetcode Golang题解之第273题整数转换英文表示
题目: 题解: var (singles []string{"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"}teens []string{&…...

使用C#手搓Word插件
WordTools主要功能介绍 编码语言:C#【VSTO】 1、选择 1.1、表格 作用:全选文档中的表格; 1.2、表头 作用:全选文档所有表格的表头【第一行】; 1.3、表正文 全选文档中所有表格的除表头部分【除第一行部分】 1.…...

WordPress主题追格企业官网主题免费开源版V1.1.6
追格企业官网主题免费开源版由追格开发的一款开源wordpress主题,专为企业建站和追格企业官网小程序(开源版)PC配套而设计,功能集新闻动态、留言反馈、产品与服务、公司简介、联系我们等模块。...

uniapp引入自定义图标
目录 一、选择图标,加入购物车 二、下载到本地 三、导入项目 四、修改字体引用路径 五、开始使用 这里以扩展iconfont图标为例 官网:iconfont-阿里巴巴矢量图标库 一、选择图标,加入购物车 二、下载到本地 直接点击下载素材࿰…...

pytorch-scheduler(调度器)
scheduler简介 scheduler(调度器)是一种用于调整优化算法中学习率的机制。学习率是控制模型参数更新幅度的关键超参数,而调度器根据预定的策略在训练过程中动态地调整学习率。 优化器负责根据损失函数的梯度更新模型的参数,而调度器则负责调整优化过程中使用的特定参数,通…...
防火墙与入侵检测系统(IDS/IPS)在现代网络安全中的关键角色
在数字化日益加速的今天,网络安全变得尤为重要。随着网络攻击的复杂性和频率不断增加,保护关键信息资产已成为各大小组织的首要任务。防火墙(Firewall)和入侵检测系统(Intrusion Detection System,IDS&…...
Python 之 os、open、json、pickle 模块的“疯狂”探险记
1.open函数的使用 Python 中的 open() 函数是处理文件的标准方法。它允许你打开一个文件,并对其进行读取、写入或追加操作 open(file,mode,encoding)函数的格式:file:文件路径 mode:打开方式(读: r写&…...

CTF-Web习题:2019强网杯 UPLOAD
题目链接:2019强网杯 UPLOAD 解题思路 打开靶场如下图所示,是一个注册和登录界面 那就注册登录一下,发现是一个提交头像的页面: 试了一下只有能正确显示的png图片才能提交成功,同时F12拿到cookie,base6…...

Unity环境渲染与反射探针的深入探索
目录 环境渲染基础 光源设置 材质与光照贴图 反射探针(Reflection Probes)详解 反射探针的创建与配置 材质中的反射探针设置 实践案例 实践案例:室内场景中的反射效果 场景设置 反射探针配置 Unity代码示例(非直接配置…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...

【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...

麒麟系统使用-进行.NET开发
文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的,如果需要进行.NET开发,则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET,所以要进…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...