如何利用OpenCV4.9 更改图像的对比度和亮度

news2024/10/11 8:25:51

返回:OpenCV系列文章目录(持续更新中......)

上一篇:使用 OpenCV 添加(混合)两个图像

下一篇:如何利用OpenCV4.9离散傅里叶变换

目标

在本教程中,您将学习如何:

  • 访问像素值
  • 用零初始化矩阵
  • 了解 cv::saturate_cast 的作用以及它为什么有用
  • 获取有关像素转换的一些很酷的信息
  • 在实际示例中提高图像的亮度

理论

注意

下面的解释属于 Richard Szeliski 的《计算机视觉:算法和应用》一书

图像处理

  • 通用图像处理运算符是获取一个或多个输入图像并生成输出图像的函数。
  • 图像变换可以看作是:
    • 点运算符(像素变换)
    • 邻域(基于区域)运营商

像素变换

  • 在这种图像处理转换中,每个输出像素的值仅取决于相应的输入像素值(可能还取决于一些全局收集的信息或参数)。
  • 此类运算符的示例包括亮度和对比度调整以及颜色校正和转换。

亮度和对比度调整

 代码:

  • 可下载代码: 点击这里

C++:

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream> 
// we're NOT "using namespace std;" here, to avoid collisions between the beta variable and std::beta in c++17
using std::cin;
using std::cout;
using std::endl;
using namespace cv; 
int main( int argc, char** argv )
{
 CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
 Mat image = imread( samples::findFile( parser.get<String>( "@input" ) ) );
 if( image.empty() )
 {
 cout << "Could not open or find the image!\n" << endl;
 cout << "Usage: " << argv[0] << " <Input image>" << endl;
 return -1;
 } 
 Mat new_image = Mat::zeros( image.size(), image.type() );
 
 double alpha = 1.0; /*< Simple contrast control */
 int beta = 0; /*< Simple brightness control */ 
 cout << " Basic Linear Transforms " << endl;
 cout << "-------------------------" << endl;
 cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha;
 cout << "* Enter the beta value [0-100]: "; cin >> beta; 
 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 );
 }
 }
 } 
 imshow("Original Image", image);
 imshow("New Image", new_image); 
 waitKey();
 return 0;
}

Jav a:

import java.util.Scanner; 
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs; 
class BasicLinearTransforms {
 private byte saturate(double val) {
 int iVal = (int) Math.round(val);
 iVal = iVal > 255 ? 255 : (iVal < 0 ? 0 : iVal);
 return (byte) iVal;
 } 
 public void run(String[] args) {
 String imagePath = args.length > 0 ? args[0] : "../data/lena.jpg";
 Mat image = Imgcodecs.imread(imagePath);
 if (image.empty()) {
 System.out.println("Empty image: " + imagePath);
 System.exit(0);
 } 
 Mat newImage = Mat.zeros(image.size(), image.type()); 
 double alpha = 1.0; /*< Simple contrast control */
 int beta = 0; /*< Simple brightness control */ 
 System.out.println(" Basic Linear Transforms ");
 System.out.println("-------------------------");
 try (Scanner scanner = new Scanner(System.in)) {
 System.out.print("* Enter the alpha value [1.0-3.0]: ");
 alpha = scanner.nextDouble();
 System.out.print("* Enter the beta value [0-100]: ");
 beta = scanner.nextInt();
 }
 byte[] imageData = new byte[(int) (image.total()*image.channels())];
 image.get(0, 0, imageData);
 byte[] newImageData = new byte[(int) (newImage.total()*newImage.channels())];
 for (int y = 0; y < image.rows(); y++) {
 for (int x = 0; x < image.cols(); x++) {
 for (int c = 0; c < image.channels(); c++) {
 double pixelValue = imageData[(y * image.cols() + x) * image.channels() + c];
 pixelValue = pixelValue < 0 ? pixelValue + 256 : pixelValue;
 newImageData[(y * image.cols() + x) * image.channels() + c]
 = saturate(alpha * pixelValue + beta);
 }
 }
 }
 newImage.put(0, 0, newImageData); 
 HighGui.imshow("Original Image", image);
 HighGui.imshow("New Image", newImage); 
 HighGui.waitKey();
 System.exit(0);
 }
} 
public class BasicLinearTransformsDemo {
 public static void main(String[] args) {
 // Load the native OpenCV library
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); 
 new BasicLinearTransforms().run(args);
 }
}

Python

from __future__ import print_function
from builtins import input
import cv2 as cv
import numpy as np
import argparse 
# Read image given by user 
parser = argparse.ArgumentParser(description='Code for Changing the contrast and brightness of an image! tutorial.')
parser.add_argument('--input', help='Path to input image.', default='lena.jpg')
args = parser.parse_args() 
image = cv.imread(cv.samples.findFile(args.input))
if image is None:
 print('Could not open or find the image: ', args.input)
 exit(0) 
new_image = np.zeros(image.shape, image.dtype) 
alpha = 1.0 # Simple contrast control
beta = 0 # Simple brightness control 
# Initialize values
print(' Basic Linear Transforms ')
print('-------------------------')
try:
 alpha = float(input('* Enter the alpha value [1.0-3.0]: '))
 beta = int(input('* Enter the beta value [0-100]: '))
except ValueError:
 print('Error, not a number') 
# Do the operation new_image(i,j) = alpha*image(i,j) + beta
# Instead of these 'for' loops we could have used simply:
# new_image = cv.convertScaleAbs(image, alpha=alpha, beta=beta)
# but we wanted to show you how to access the pixels :) 
for y in range(image.shape[0]):
 for x in range(image.shape[1]):
 for c in range(image.shape[2]):
 new_image[y,x,c] = np.clip(alpha*image[y,x,c] + beta, 0, 255) 
cv.imshow('Original Image', image)
cv.imshow('New Image', new_image) 
# Wait until user press some key
cv.waitKey()

解释

我们使用 cv::imread 加载图像并将其保存在 Mat 对象中:

 CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
 Mat image = imread( samples::findFile( parser.get<String>( "@input" ) ) );
 if( image.empty() )
 {
 cout << "Could not open or find the image!\n" << endl;
 cout << "Usage: " << argv[0] << " <Input image>" << endl;
 return -1;
 }

Java:

 String imagePath = args.length > 0 ? args[0] : "../data/lena.jpg";
 Mat image = Imgcodecs.imread(imagePath);
 if (image.empty()) {
 System.out.println("Empty image: " + imagePath);
 System.exit(0);
 }

Python :

parser = argparse.ArgumentParser(description='Code for Changing the contrast and brightness of an image! tutorial.')
parser.add_argument('--input', help='Path to input image.', default='lena.jpg')
args = parser.parse_args() 
image = cv.imread(cv.samples.findFile(args.input))
if image is None:
 print('Could not open or find the image: ', args.input)
 exit(0)
  • 现在,由于我们将对这个图像进行一些转换,我们需要一个新的 Mat 对象来存储它。此外,我们希望它具有以下功能:
    • 初始像素值等于零
    • 与原始图像的大小和类型相同

 c++:

new_image = np.zeros(image.shape, image.dtype)

 Java

Mat newImage = Mat.zeros(image.size(), image.type());

python

new_image = np.zeros(image.shape, image.dtype)

我们观察到 cv::Mat::zeros 返回一个基于 image.size()和 image.type()的 Matlab 风格的零初始值设定项

  • 我们现在问的价值观a和b由用户输入:

C++ 

 double alpha = 1.0; /*< Simple contrast control */
 int beta = 0; /*< Simple brightness control */ 
 cout << " Basic Linear Transforms " << endl;
 cout << "-------------------------" << endl;
 cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha;
 cout << "* Enter the beta value [0-100]: "; cin >> beta;

Java

 double alpha = 1.0; /*< Simple contrast control */
 int beta = 0; /*< Simple brightness control */ 
 System.out.println(" Basic Linear Transforms ");
 System.out.println("-------------------------");
 try (Scanner scanner = new Scanner(System.in)) {
 System.out.print("* Enter the alpha value [1.0-3.0]: ");
 alpha = scanner.nextDouble();
 System.out.print("* Enter the beta value [0-100]: ");
 beta = scanner.nextInt();
 }

Python 

alpha = 1.0 # Simple contrast control
beta = 0 # Simple brightness control 
# Initialize values
print(' Basic Linear Transforms ')
print('-------------------------')
try:
 alpha = float(input('* Enter the alpha value [1.0-3.0]: '))
 beta = int(input('* Enter the beta value [0-100]: '))
except ValueError:
 print('Error, not a number')

 

C++: 

 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 );
 }
 }
 }

Java

 byte[] imageData = new byte[(int) (image.total()*image.channels())];
 image.get(0, 0, imageData);
 byte[] newImageData = new byte[(int) (newImage.total()*newImage.channels())];
 for (int y = 0; y < image.rows(); y++) {
 for (int x = 0; x < image.cols(); x++) {
 for (int c = 0; c < image.channels(); c++) {
 double pixelValue = imageData[(y * image.cols() + x) * image.channels() + c];
 pixelValue = pixelValue < 0 ? pixelValue + 256 : pixelValue;
 newImageData[(y * image.cols() + x) * image.channels() + c]
 = saturate(alpha * pixelValue + beta);
 }
 }
 }
 newImage.put(0, 0, newImageData);

Python: 

for y in range(image.shape[0]):
 for x in range(image.shape[1]):
 for c in range(image.shape[2]):
 new_image[y,x,c] = np.clip(alpha*image[y,x,c] + beta, 0, 255)

请注意以下事项(仅限 C++ 代码):

  • 要访问图像中的每个像素,我们使用以下语法:image.at<Vec3b>(y,x)[c]其中 y 是行,x 是列,c 是 B、G 或 R(0、1 或 2)。
  • 自操作以来a.p(ij),可以给出超出范围或不是整数的值(如果a是浮点数),我们使用 cv::saturate_cast 来确保值有效。
  • 最后,我们创建窗口并按照通常的方式显示图像。
# Show stuff
cv.imshow('Original Image', image)
cv.imshow('New Image', new_image) 
# Wait until user press some key
cv.waitKey()

注意

与其使用 for 循环来访问每个像素,不如简单地使用以下命令:

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

其中 cv::Mat::convertTo 将有效地执行 *new_image = a*image + beta*。但是,我们想向您展示如何访问每个像素。无论如何,这两种方法都给出相同的结果,但 convertTo 更优化并且工作速度更快。

结果

 

$ ./BasicLinearTransforms lena.jpg
Basic Linear Transforms
-------------------------
* Enter the alpha value [1.0-3.0]: 2.2
* Enter the beta value [0-100]: 50

我们得到这个: 

实例

在本段中,我们将把我们所学到的知识付诸实践,通过调整图像的亮度和对比度来纠正曝光不足的图像。我们还将看到另一种校正图像亮度的技术,称为伽马校正。

亮度和对比度调整

浅灰色,原始图像的直方图,当亮度 = 80 时,深灰色

直方图表示每个颜色级别的像素数。深色图像将具有许多低色值的像素,因此直方图将在其左侧呈现一个峰值。添加恒定偏置时,直方图向右移动,因为我们已向所有像素添加了恒定偏置。

浅灰色,原始图像的直方图,当 Gimp 中的对比度< 0 时,深灰色

伽玛校正

伽玛校正可用于通过在输入值和映射的输出值之间使用非线性变换来校正图像的亮度:

由于这种关系是非线性的,因此所有像素的效果都不会相同,并且取决于它们的原始值。

绘制不同 gamma 值的图

校正曝光不足的图像

作者:Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons

整体亮度有所提高,但您可以注意到,由于所用实现的数值饱和度(摄影中的高光剪切),云层现在非常饱和。

作者:Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons

伽玛校正应该倾向于增加较少的饱和效应,因为映射是非线性的,并且不可能像以前的方法那样出现数值饱和。

左图:alpha 后的直方图,beta 校正;中心:原始图像的直方图;右图:伽马校正后的直方图

上图比较了三个图像的直方图(三个直方图之间的 y 范围不同)。您可以注意到,大多数像素值都位于原始图像直方图的下部。后a、b校正后,由于饱和度以及右移,我们可以观察到 255 处的大峰值。伽玛校正后,直方图向右移动,但暗区域的像素比亮区域的像素偏移更多(参见伽马曲线图)。

在本教程中,您介绍了两种调整图像对比度和亮度的简单方法。它们是基本技术,不能用作光栅图形编辑器的替代品!

代码:

本教程的代码在这里。伽玛校正代码:

 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 = img.clone();
 LUT(img, lookUpTable, res);

查找表用于提高计算性能,因为只需计算一次 256 个值。

参考文献:

1、《Changing the contrast and brightness of an image!》----Ana Huamán

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

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

相关文章

路由的完整使用

多页面和单页面 多页面是指超链接等跳转到另一个HTML文件,单页面是仍是这个文件只是路由改变了页面的一部分结构. 路由的基本使用 使用vue2,则配套的路由需要是第3版. 1)下载vue-router插件 2)引入导出函数 3)new 创建路由对象 4)当写到vue的router后只能写路由对象,因此只…

Git版本管理使用手册 - 8 - 合并分支、解决冲突

合并整个开发分支 切换到本地test分支&#xff0c;选择右下角远程开发分支&#xff0c;选择Merge into Current。然后提交到远程test仓库。 合并某次提交的代码 当前工作区切换成test分支&#xff0c;选择远程仓库中的dev开发分支&#xff0c;选择需要合并的提交版本右击&a…

cmd小黑框——命令行基础语句与实操效果演示

cmd小黑框——命令行学习 初识cmd小黑框cmd命令缩写含义介绍cmd基础操作cmd实操效果演示cmd命令行快捷键 初识cmd小黑框 CMD&#xff0c;全称Command&#xff0c;是Windows系统中自带的一个命令行解释器&#xff0c;它允许用户通过输入命令来对系统进行各种操作。CMD命令在Win…

从运营层面看财务管理:如何做好项目的财务预算

有效的项目运营是企业发展进步的主要活动&#xff0c;企业管理者的项目财务管理主要针对项目财务预算。项目财务预算管理是企业财务管理的一个分支&#xff0c;也是项目财务管理的核心部分&#xff0c;其表现形式为一个综合性的财务计划&#xff0c;主要包括预算编制、报告、执…

SQL Server 实验二:数据库视图的创建和使用

目录 第一关 相关知识 什么是表 操作数据表 创建数据表 插入数据 修改表结构 删除数据表 编程要求 第一关实验代码&#xff1a; 第二关 相关知识 视图是什么 视图的优缺点 视图的优点 视图的缺点 操作视图 创建视图 通过视图向基本表中插入数据 通过视图修改基本表的…

武汉星起航引领跨境电商新潮流,一站式方案助力创业者快速崛起

在跨境电商领域&#xff0c;武汉星起航电子商务有限公司以其独特的一站式解决方案&#xff0c;为众多创业者提供了通往成功的捷径。该公司凭借专业的团队和丰富的经验&#xff0c;为创业者提供全方位的支持&#xff0c;助力他们在跨境电商领域实现快速发展。 随着全球经济的不…

线性规划-非线性规划-非线性规划遗传算法

遗传算法当前获得的最优值作为非线性寻优的初始解&#xff0c;随后进行求解。

从创意立项到产品赚钱的全调优过程复盘,如何提高产品存活率 | TopOn变现干货

10月28日&#xff0c;由TopOn、罗斯基联合主办的“游戏赛道新机会”主题沙龙在成都举办。活动邀请了国内外多位知名公司及游戏爆款产品的负责人分享&#xff0c;分别从各自的方向及经验出发&#xff0c;以数据、案例、产品分析、行业趋势等多个维度&#xff0c;为行业从业者带来…

C++初阶篇----string类

目录 引言标准库中的string类string类的常用接口string类对象的常见构造string类对象的string类对象的访问及遍历string类对象的修改string类非成员函数 引言 什么是string类&#xff1f; string 类是 C 标准库中的一个类&#xff0c;用于处理字符串。它提供了一系列方法来创建…

北斗激光平地机提高农机耕种效率

北斗激光平地机提高农机耕种效率 湖北省浠水县地处大别山南麓&#xff0c;六成左右的田块都分布在丘陵地带&#xff0c;田块小、高低落差大&#xff0c;给机械化作业带来诸多不便。在今年的春耕中&#xff0c;配备北斗智能检测终端的激光平地机很受当地种粮大户追捧。 稻田平整…

超声波清洗机选购指南:眼镜清洗器哪个好?4款眼镜清洗利器推荐

随着科技的发展&#xff0c;现在就是连洗眼镜都有专门的辅助工具了&#xff0c;没错&#xff0c;就是超声波眼镜清洗机&#xff01;这种超声波清洗机之所以能够做到清洁眼镜&#xff0c;是因为它利用了超声波振动原理&#xff0c;通过水分子爆破瞬间的冲击力对眼镜上面的污垢进…

2024 年广西职业院校技能大赛高职组《云计算应用》赛项样卷

#需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包及镜…

Java八股文(设计模式)

Java八股文の设计模式 设计模式 设计模式 什么是设计模式&#xff1f;请列举一些常见的设计模式。 设计模式是软件设计中常用的一种思维模式&#xff0c;它描述了一类具有相似特征和解决思路的问题。 常见的设计模式包括单例模式、工厂模式、观察者模式、装饰器模式等。 请解释…

简单的LAMP部署

目录 一、准备环境 二、安装apache组件 三、安装mysql组件 四、安装php组件 五、浏览器访问 一、准备环境 iptables -F #清空防火墙规则 systemctl stop firewalld #关闭防火墙 setenforce 0 …

IP/TCP--解决为什么电脑连上了有线网就不能再连WIFI【转载】

文章目录 第一种情况&#xff1a;WIFI与有线网在同一网段下1、查看路由信息2、调整跃点数 第二种情况&#xff1a;WIFI与有线网不在同一网段下跃点数概念路由器设置入口 【注意适用情型&#xff1a;需要同时用到内网&#xff08;不能上公网的内部网络&#xff09;和互联网。】 …

PyQt5——QFileDialog 打开文件对话框

概述 打开文件对话框是为了让用户可以自己选择要打开的文件&#xff0c;在 PyQt5 里要打开文件对话框可以使用 QFileDialog。 无父类窗口 Python PyQt5 打开文件对话框要使用 QFileDialog.getOpenFileName()&#xff0c;如果没有父类 Widget 的话&#xff0c;QFileDialog.ge…

Webpack常见插件和模式

目录 目录 目录认识 PluginCleanWebpackPluginHtmlWebpackPlugin自定义模版 DefinePlugin的介绍 ( 持续更新 )Mode 配置 认识 Plugin Loader是用于特定的模块类型进行转换&#xff1b; Plugin可以用于执行更加广泛的任务&#xff0c;比如打包优化、资源管理、环境变量注入等 …

linux离线安装maven

一、下载maven 地址&#xff1a;Maven – Download Apache Maven 使用root权限用户登录服务器 cd /opt sudo mkdir maven cd maven 二、上传maven 使用Xftp工具 三、解压并配置环境变量 tar -zxvf tar -zxvf apache-maven-3.9.6-bin.tar.gz cd apache-maven-3.9.6/ 看到解压…

三极管工作方式

如下图&#xff1a; 谨记&#xff1a; NPN 型&#xff1a; B 0 截止 B 1 导通 PNP 型&#xff1a; B 0 导通 B 1 截止 来源&#xff1a;% - 闲鹤

面对1.2亿月活的UGC平台,游戏开发者有哪些机会? | TOPON变现干货

4月21日&#xff0c;TopOn、七麦数据、罗斯基共同主办的《游戏赛道新机会》主题沙龙在深圳举办。活动邀请到了国内多家知名厂商和平台的负责人&#xff0c;大家从个人业务的角度出发&#xff0c;为从业者分享最新的行业趋势和方法论。 在活动上&#xff0c;迷你玩内容生态运营…