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

返回: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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙OS开发实例:【工具类封装-首选项本地存储】

import dataPreferences from ohos.data.preferences; import bundleManager from ohos.bundle.bundleManager; 本地首选项数据的保存&#xff0c;利用key value 【使用要求】 DevEco Studio 3.1.1 Release api 9 【使用示例】 1、app启动时&#xff0c;从本地读取数据&…

Spring Integration 是什么?

Spring Integration 是什么&#xff1f; Spring Integration 在 Spring 家族不太有名气&#xff0c;如果不是有需求&#xff0c;一般也不会仔细去看。那么 Spring Integration 是什么呢&#xff1f;用官方的一句话来解释就是&#xff1a;它是一种轻量级消息传递模块&#xff0…

干货分享:品牌如何通过社媒激发年轻人消费力?

随着年轻人的消费愈发理性&#xff0c;年轻人在消费时更偏向于熟人种草场景下的信任决策&#xff0c;社交媒体成为品牌吸引用户的必争之地。今天媒介盒子就来和大家聊聊&#xff1a;品牌如何通过社媒激发年轻人消费力。 一、 激发用户共鸣&#xff0c;与用户产生情感连接。 虽…

通达信指标公式--通达信波段极品抄底指标公式,波段顶部和底部的判断

今日分享的通达信波段极品抄底指标公式&#xff0c;是一个波段顶底的提示指标。 具体信号说明&#xff1a; 当指标信号柱出现洋红柱子时&#xff0c;是波段底的信号&#xff0c;此时是参考持股的信号。 抄底信号出现在黄色直线附近较好&#xff0c;出现在背离走势更好。懂波浪…

【正点原子FreeRTOS学习笔记】————(12)信号量

这里写目录标题 一、信号量的简介&#xff08;了解&#xff09;二、二值信号量&#xff08;熟悉&#xff09;三、二值信号量实验&#xff08;掌握&#xff09;四、计数型信号量&#xff08;熟悉&#xff09;五、计数型信号量实验&#xff08;掌握&#xff09;六、优先级翻转简介…

[优选算法专栏]专题十五:FloodFill算法(一)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

缓冲区溢出漏洞相关知识点汇总

1.缓冲区基础知识相关定义 缓冲区定义&#xff1a;缓冲区一块连续的内存区域&#xff0c;用于存放程序运行时&#xff0c;加载到内存的运行代码和数据。 缓冲区溢出&#xff1a;缓冲区溢出是指程序运行时&#xff0c;向固定大小的缓冲区写入超过其容量的数据。多余的数据会越…

DFS:从递归去理解深度优先搜索

一、深入理解递归 二、递归vs迭代 三、深入理解搜索、回溯和剪枝 四、汉诺塔问题 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public: //笔试题&#xff0c;不讲武德&#xff0c;CAvoid move(int n,vector<int>& A, vector<int>& B, ve…

充值活动倒计时!快来get您的春日豪礼

春分已至 万物生辉 就在上周末 马拉松赛事霸屏朋友圈 不论是燃动全城的汉马 还是集结万人的锡马 马拉松精神给予我们的是 挑战自我、永不言弃 奋力前行、昂扬向上的力量 在这万物复苏的阳春三月 正是潜心钻研 奋力拼搏的好时节 神工坊为广大仿真行业科技工作者 送上春…

净化室内空气有妙招,约克VRF甲醛净化中央空调给全家人舒适守护

早春3月&#xff0c;春回大地&#xff0c;又到了万物复苏、草长莺飞的季节&#xff0c;但对于我们的呼吸道来说&#xff0c;这又是个高危时期。伴随气温的不断上升&#xff0c;各种细菌、病毒开始活跃起来&#xff0c;同时&#xff0c;春季也是花粉过敏的高发期。无论是甲醛、细…

因子分析全流程汇总

探索性因子分析&#xff08;以下简称因子分析&#xff09;&#xff0c;是毕业论文中常用的数据分析方法&#xff0c;用于研究多个变量之间的关系&#xff0c;并通过提取公共因子来简化数据集。 信息浓缩是因子分析最常见的应用&#xff0c;除此之外&#xff0c;因子分析还可以…

2.3 同步与互斥

1 2 3 4 5 6 7 8 9 10 11 12

【InternLM 实战营第二期笔记】书生·浦语大模型全链路开源体系及InternLM2技术报告笔记

大模型 大模型成为发展通用人工智能的重要途径 专用模型&#xff1a;针对特定任务&#xff0c;一个模型解决一个问题 通用大模型&#xff1a;一个模型应对多种任务、多种模态 书生浦语大模型开源历程 2023.6.7&#xff1a;InternLM千亿参数语言大模型发布 2023.7.6&#…

【ML】类神经网络训练不起来怎么办 5

【ML】类神经网络训练不起来怎么办 5 1. Saddle Point V.S. Local Minima(局部最小值 与 鞍点)2. Tips for training: Batch and Momentum(批次与 动量)2.1 Tips for training: Batch and Momentum2.2 参考文献:2.3 Gradient Descent2.4 Concluding Remarks(前面三讲)3.…

2024年AI威胁场景报告:揭示现今最大的AI安全挑战

AI正彻底改变每一个数据驱动的机会&#xff0c;有可能带来一个繁荣的新时代&#xff0c;让人类的生活质量达到难以想象的高度。但就像任何突破性的新技术一样&#xff0c;伟大的潜力往往蕴含着巨大的风险。 AI在很大程度上是有史以来部署在生产系统中的最脆弱的技术。它在代码…

寒冬继续!飞书发全员信 “适当精简团队规模”

多精彩内容在公众号。 3月26日飞书CEO谢欣发布全员信&#xff0c;宣布进行组织调整&#xff0c;同时为受到影响的“同学”提供补偿方案和转岗机会。 在致员工的一封信中&#xff0c;谢欣坦诚地指出&#xff0c;尽管飞书的团队人数众多&#xff0c;但组织结构的不够紧凑导致了工…

使用HarmonyOS实现图片编辑,裁剪、旋转、亮度、透明度

介绍 本篇Codelab是基于ArkTS的声明式开发范式的样例&#xff0c;主要介绍了图片编辑实现过程。样例主要包含以下功能&#xff1a; 图片的解码。使用PixelMap进行图片编辑&#xff0c;如裁剪、旋转、亮度、透明度、饱和度等。图片的编码。 相关概念 图片解码&#xff1a;读取…

经典机器学习模型(九)EM算法的推导

经典机器学习模型(九)EM算法的推导 1 相关数据基础 1.1 数学期望 1.1.1 数学期望的定义 根据定义&#xff0c;我们可以求得掷骰子对应的期望&#xff1a; E ( X ) X 1 ∗ p ( X 1 ) X 2 ∗ p ( X 2 ) . . . X 6 ∗ p ( X 6 ) 1 ∗ 1 6 2 ∗ 1 6 1 ∗ 1 6 3 ∗ 1 6 …

【考研数学】跟武忠祥,如何搭配汤家凤《1800》?

可以但不建议&#xff01;正所谓原汤化原食&#xff0c;你做1800&#xff0c;当然是听汤神的更合适&#xff01; 汤家凤与武忠祥的讲课风格真的大不相同&#xff01;汤老师特别注重基础和题量&#xff0c;让你在数理思维上打下扎实的根基。而武老师则更偏向于深厚的理论&#…

天地图如何获取多边形面积

目录 一、初始化地图 二、创建polygonTool 三、多边形获取面积 ​四、完整代码&#xff08;包括添加点、添加面、编辑面、获取面积&#xff09; 项目中提出在地图上绘制面并获取面积&#xff0c;如何实现&#xff1f; 在天地图官网的JavaScript API 中&#xff0c;链接如下…