[OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡
本文主要介绍基于图像强度变换算法来实现图像对比度均衡。通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值。本文主要通过OpenCV contrib中的intensity_transform模块实现图像对比度均衡。如果想了解具体相关方法原理见冈萨雷斯主编的图像处理经典书籍 数字图像处理Digital Image Processing 第四版第三章。
本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:
本文所有代码见:
文章目录
1 相关知识介绍
1.1 图像强度
图像强度的英文名称是image intensity,意思是单通道图像像素的值大小。在灰度图像中,图像强度是就是图像的灰度级。在RGB颜色空间中,可以理解为RGB三个通道的像素灰度值,即RGB包含三种图像强度。其他颜色空间也是同样的道理。
1.2 图像对比度
对比度是指图像中物体在亮度或颜色上的差异,对比度使图像中一个物体区别于同一视场内的其他物体。对比度越大,图像类各个物体的颜色差别就越大,图像也就越鲜艳。
如下图所示。显然,左图像的对比度较低,因为与右图像相比,很难识别图像中存在的细节。

现实生活中的例子可以是晴天和大雾天。在阳光明媚的日子里,我们觉得一切都很清晰,因此与雾天相比,一切看起来几乎都一样强烈(暗淡、灰暗)。晴天的图像代码对比度高,雾天代表对比度低。
一种更有效的检查图像对比度是低还是高的方法是绘制图像直方图,让我们为上面的图像绘制直方图。如下图所示:
 
很明显,从左边的图像直方图中,我们可以看到图像强度值位于一个狭窄的范围内。因为很难区分几乎相同的强度值,因此左图像的对比度较低。如果不理解可以看看下面灰度范围图,可以看到灰度变化范围越大,可视化区分度越好。因此,对于高对比度,图像直方图应该跨越整个动态范围。
 
到目前为止,我们讨论了对比度,但没有讨论低对比度图像的原因。低对比度图像可能是由于照明不足、成像传感器缺乏动态范围,甚至在图像采集过程中镜头光圈设置错误等原因造成的。因此我们需要对低对比度的图像进行图像增强。
1.3 OpenCV中基于图像强度的对比度增强算法
OpenCV contrib中的intensity_transform模块包含于图像强度的对比度增强算法。主要包括的算法有:
- 自适应直方图均衡化 Autoscaling
 - 对数变换 Log Transformations
 - gamma变换 Power-Law (Gamma) Transformations
 - 对比度拉伸 Contrast Stretching
 - BIMEF, A Bio-Inspired Multi-Exposure Fusion Framework for Low-light Image enhancement
 
OpenCV contrib的intensity_transform模块官方代码仓库见:intensity_transform
BIMEF算法,是一个C++实现的原始MATLAB算法。与原始代码相比,此实现速度稍慢,并且无法提供相同的结果。特别是,在一定条件下,对于明亮区域,图像增强的质量会降低,而且OpenCV需要engine库才能运行BIMEF算法,所以本文就不介绍该算法。
关于图像强度的进一步详细介绍见:图像增强综述
2 代码与结果分析
2.1 调用接口说明
本文介绍OpenCV contrib的intensity_transform模块中四种图像强度增强算法。所有图像增加代码都在intensity_transform模块中。本文提供C++和Python版本的实现,不同图像强度增强算法调用接口如下:
C++
// Apply intensity transformations
// 应用强度转换
Mat imgAutoscaled, imgLog;
// autoscaling
autoscaling(g_image, imgAutoscaled);
// gamma变换
gammaCorrection(g_image, g_imgGamma, g_gamma / 100.0f);
// 对数变换
logTransform(g_image, imgLog);
// 对比度拉伸
contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2);
Python
# Apply intensity transformations
# 应用强度转换
# autoscaling
imgAutoscaled = np.zeros(g_image.shape, np.uint8)
cv2.intensity_transform.autoscaling(g_image, imgAutoscaled)
# gamma变换
g_imgGamma = np.zeros(g_image.shape, np.uint8)
cv2.intensity_transform.gammaCorrection(g_image, g_imgGamma, g_gamma / 100.0)
# 对数变换
imgLog = np.zeros(g_image.shape, np.uint8)
cv2.intensity_transform.logTransform(g_image, imgLog)
# 对比度拉伸
g_contrastStretch = np.zeros(g_image.shape, np.uint8)
cv2.intensity_transform.contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2)
不同的方法所需要设定的参数不同,具体如下:
- autoscaling:对输入图像进行自适应缩放以增强对比度,仅需要输入待增强的图像。
 - gamma变换:对输入图像进行伽马校正以增强对比度,需要输入待增强图像和参数gamma。
 - 对数变换:对输入图像进行对数转换以增强对比度,仅需要输入待增强的图像。
 - 对比度拉伸:对输入图像应用线性对比度拉伸以增强对比度,需要输入待增强图像和参数r1,s1,r2,s2,(r1,s1)和(r2,s2)为转换函数第一个点和第二个点的坐标。
 
此外为了比较不同图像强度增强方法的效果,加入了图像对比度计算方法
图像对比度计算方法为RMS Contrast,来自于How to calculate the contrast of an image?
方法原理很简单,就是将图像变为灰度图,然后计算图像方差。
2.2 完整代码
代码功能很简单,就是获得输入图像,然后对输入图像应用不同的图像增强算法。对于可调参数的,创建滑动条以调整方法的输入参数。但是要注意的是,输入图像必须为三通道RGB图像。C++和Python代码如下:
C++
#include <opencv2/opencv.hpp>
#include <opencv2/intensity_transform.hpp>
#include <iostream>
using namespace std;
using namespace cv;
using namespace cv::intensity_transform;
// 计算对比度
double rmsContrast(Mat srcImg)
{
	Mat dstImg, dstImg_mean, dstImg_std;
	// 灰度化
	cvtColor(srcImg, dstImg, COLOR_BGR2GRAY);
	// 计算图像均值和方差
	meanStdDev(dstImg, dstImg_mean, dstImg_std);
	// 获得图像对比度
	double contrast = dstImg_std.at<double>(0, 0);
	return contrast;
}
// 设置命名空间避免污染用户变量
namespace
{
	// global variables
	Mat g_image;
	// gamma变换变量
	int g_gamma = 40;
	const int g_gammaMax = 500;
	Mat g_imgGamma;
	const std::string g_gammaWinName = "Gamma Correction";
	// 对比度拉伸
	Mat g_contrastStretch;
	int g_r1 = 70;
	int g_s1 = 15;
	int g_r2 = 120;
	int g_s2 = 240;
	const std::string g_contrastWinName = "Contrast Stretching";
	// 创建gamma变换滑动条
	static void onTrackbarGamma(int, void*)
	{
		float gamma = g_gamma / 100.0f;
		gammaCorrection(g_image, g_imgGamma, gamma);
		imshow(g_gammaWinName, g_imgGamma);
		cout << g_gammaWinName << ": " << rmsContrast(g_imgGamma) << endl;
	}
	// 创建对数变换滑动条
	static void onTrackbarContrastR1(int, void*)
	{
		contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2);
		imshow("Contrast Stretching", g_contrastStretch);
		cout << g_contrastWinName << ": " << rmsContrast(g_contrastStretch) << endl;
	}
	static void onTrackbarContrastS1(int, void*)
	{
		contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2);
		imshow("Contrast Stretching", g_contrastStretch);
		cout << g_contrastWinName << ": " << rmsContrast(g_contrastStretch) << endl;
	}
	static void onTrackbarContrastR2(int, void*)
	{
		contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2);
		imshow("Contrast Stretching", g_contrastStretch);
		cout << g_contrastWinName << ": " << rmsContrast(g_contrastStretch) << endl;
	}
	static void onTrackbarContrastS2(int, void*)
	{
		contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2);
		imshow("Contrast Stretching", g_contrastStretch);
		cout << g_contrastWinName << ": " << rmsContrast(g_contrastStretch) << endl;
	}
}
int main()
{
	// 图像路径
	const std::string inputFilename = "./image/tree.jpg";
	// Read input image
	// 读图
	g_image = imread(inputFilename);
	if (g_image.empty())
	{
		printf("image is empty");
		return 0;
	}
	// Create trackbars
	// 创建滑动条
	namedWindow(g_gammaWinName);
	// 创建gamma变换筛选方法
	createTrackbar("Gamma value", g_gammaWinName, &g_gamma, g_gammaMax, onTrackbarGamma);
	// 对比度拉伸 Contrast Stretching
	namedWindow(g_contrastWinName);
	createTrackbar("Contrast R1", g_contrastWinName, &g_r1, 256, onTrackbarContrastR1);
	createTrackbar("Contrast S1", g_contrastWinName, &g_s1, 256, onTrackbarContrastS1);
	createTrackbar("Contrast R2", g_contrastWinName, &g_r2, 256, onTrackbarContrastR2);
	createTrackbar("Contrast S2", g_contrastWinName, &g_s2, 256, onTrackbarContrastS2);
	// Apply intensity transformations
	// 应用强度转换
	Mat imgAutoscaled, imgLog;
	// autoscaling
	autoscaling(g_image, imgAutoscaled);
	// gamma变换
	gammaCorrection(g_image, g_imgGamma, g_gamma / 100.0f);
	// 对数变换
	logTransform(g_image, imgLog);
	// 对比度拉伸
	contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2);
	// Display intensity transformation results
	// 展示结果
	imshow("Original Image", g_image);
	cout << "Original Image: " << rmsContrast(g_image) << endl;
	imshow("Autoscale", imgAutoscaled);
	cout << "Autoscale: " << rmsContrast(imgAutoscaled) << endl;
	imshow(g_gammaWinName, g_imgGamma);
	cout << g_gammaWinName << ": " << rmsContrast(g_imgGamma) << endl;
	imshow("Log Transformation", imgLog);
	cout << "Log Transformation: " << rmsContrast(imgLog) << endl;
	imshow(g_contrastWinName, g_contrastStretch);
	cout << g_contrastWinName << ": " << rmsContrast(g_contrastStretch) << endl;
	waitKey(0);
	return 0;
}
Python
# -*- coding: utf-8 -*-
"""
Created on Thu Sep 10 18:48:56 2020
@author: luohenyueji
"""
import cv2
import numpy as np
# ----- 全局变量
# 输入图片
g_image = np.zeros((3, 3, 3), np.uint8)
# gamma变换变量
g_gamma = 40
g_gammaMax = 500
g_gammaWinName = "Gamma Correction"
# 对比度拉伸
g_r1 = 70
g_s1 = 15
g_r2 = 120
g_s2 = 240
g_contrastWinName = "Contrast Stretching"
# 创建gamma变换滑动条
def onTrackbarGamma(x):
    g_gamma = x
    gamma = g_gamma / 100.0
    g_imgGamma = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.gammaCorrection(g_image, g_imgGamma, gamma)
    cv2.imshow(g_gammaWinName, g_imgGamma);
    print(g_gammaWinName + ": " + str(rmsContrast(g_imgGamma)))
# 创建对数变换滑动条
def onTrackbarContrastR1(x):
    g_r1 = x
    g_contrastStretch = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2)
    cv2.imshow("Contrast Stretching", g_contrastStretch)
    print(g_contrastWinName + ": " + str(rmsContrast(g_contrastStretch)))
def onTrackbarContrastS1(x):
    g_s1 = x
    g_contrastStretch = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2)
    cv2.imshow("Contrast Stretching", g_contrastStretch)
    print(g_contrastWinName + ": " + str(rmsContrast(g_contrastStretch)))
def onTrackbarContrastR2(x):
    g_r2 = x
    g_contrastStretch = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2)
    cv2.imshow("Contrast Stretching", g_contrastStretch)
    print(g_contrastWinName + ": " + str(rmsContrast(g_contrastStretch)))
def onTrackbarContrastS2(x):
    g_s2 = x
    g_contrastStretch = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2)
    cv2.imshow("Contrast Stretching", g_contrastStretch)
    print(g_contrastWinName + ": " + str(rmsContrast(g_contrastStretch)))
# 计算对比度
def rmsContrast(scrImg):
    dstImg = cv2.cvtColor(scrImg, cv2.COLOR_BGR2GRAY)
    contrast = dstImg.std()
    return contrast
def main():
    # 图像路径
    inputFilename = "./image/car.png"
    # 读图
    global g_image
    g_image = cv2.imread(inputFilename)
    if g_image is None:
        print("image is empty")
        return
    # 创建滑动条
    cv2.namedWindow(g_gammaWinName)
    # 创建gamma变换筛选方法
    cv2.createTrackbar("Gamma value", g_gammaWinName, g_gamma, g_gammaMax, onTrackbarGamma)
    # 对比度拉伸 Contrast Stretching
    cv2.namedWindow(g_contrastWinName)
    cv2.createTrackbar("Contrast R1", g_contrastWinName, g_r1, 256, onTrackbarContrastR1)
    cv2.createTrackbar("Contrast S1", g_contrastWinName, g_s1, 256, onTrackbarContrastS1)
    cv2.createTrackbar("Contrast R2", g_contrastWinName, g_r2, 256, onTrackbarContrastR2)
    cv2.createTrackbar("Contrast S2", g_contrastWinName, g_s2, 256, onTrackbarContrastS2)
    # Apply intensity transformations
    # 应用强度转换
    # autoscaling
    imgAutoscaled = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.autoscaling(g_image, imgAutoscaled)
    # gamma变换
    g_imgGamma = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.gammaCorrection(g_image, g_imgGamma, g_gamma / 100.0)
    # 对数变换
    imgLog = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.logTransform(g_image, imgLog)
    # 对比度拉伸
    g_contrastStretch = np.zeros(g_image.shape, np.uint8)
    cv2.intensity_transform.contrastStretching(g_image, g_contrastStretch, g_r1, g_s1, g_r2, g_s2)
    # 展示结果
    cv2.imshow("Original Image", g_image);
    print("Original Image: " + str(rmsContrast(g_image)))
    cv2.imshow("Autoscale", imgAutoscaled)
    print("Autoscale: " + str(rmsContrast(imgAutoscaled)))
    cv2.imshow(g_gammaWinName, g_imgGamma)
    print(g_gammaWinName + ": " + str(rmsContrast(g_imgGamma)))
    cv2.imshow("Log Transformation", imgLog)
    print("Log Transformation: " + str(rmsContrast(imgLog)))
    cv2.imshow(g_contrastWinName, g_contrastStretch)
    print(g_contrastWinName + ": " + str(rmsContrast(g_contrastStretch)))
    cv2.waitKey(0)
if __name__ == '__main__':
    main()
2.3 测试与结果评价
2.3.1 测试结果
测试图片部分来自于intensity_transformations。本文分别对四种不同场景进行了测试,其中Gamma Correction和Contrast Stretching是手动调整参数后个人觉得最好结果。具体结果如下:
场景1 car
| 类型 | 结果 | 
|---|---|
| 原图 | ![]()  | 
| Autoscaling | ![]()  | 
| Gamma Correction | ![]()  | 
| Contrast Stretching | ![]()  | 
| Log Transformations | ![]()  | 
场景2 tree
| 类型 | 结果 | 
|---|---|
| 原图 | ![]()  | 
| Autoscaling | ![]()  | 
| Gamma Correction | ![]()  | 
| Contrast Stretching | ![]()  | 
| Log Transformations | ![]()  | 
场景3 xray
| 类型 | 结果 | 
|---|---|
| 原图 | ![]()  | 
| Autoscaling | ![]()  | 
| Gamma Correction | ![]()  | 
| Contrast Stretching | ![]()  | 
| Log Transformations | ![]()  | 
场景4 indicator
| 类型 | 结果 | 
|---|---|
| 原图 | ![]()  | 
| Autoscaling | ![]()  | 
| Gamma Correction | ![]()  | 
| Contrast Stretching | ![]()  | 
| Log Transformations | ![]()  | 
2.3.2 结果评价
总结不同算法在四个场景表现如下:
- Autoscaling:Autoscaling适用于原始图像本身比较模糊的场景,如果原始图像不模糊则没什么太大改进,但各个环境下总体效果不错。
 - Gamma Correction:Gamma Correction所需要调整的参数仅有一个,在各种场景下稍微调整参数就能获得不错的结果。
 - Contrast Stretching:Contrast Stretching在各个场景都能获得特别好的效果,但是需要调整的参数太多。
 - Log Transformations:Log Transformations仅仅适用于亮度极低的场景,其他场景增强后对比度反而更差。
 
总结来说,如果对比度影响不那么大或者需要自动化,autoscaling足以对付绝大部分场景,事实上autoscaling用的也算最多的方式。如果对图像对比度要求特别高,通常都是自动参数寻优+Contrast Stretching+图像对比度结果评价来应用,通过设定不同的参数,然后使用Contrast Stretching对图像进行处理,最后筛选图像对比度最高的一次作为最后结果,但是这种方式可能需要一定处理时间,不过确实是一个很不错的解决方案。在实际场景,结合autoscaling和Contrast Stretching自动寻找参,找对比度最好结果即可。
3 参考
3.1 参考代码
3.2 参考文章
- Histogram Equalization
 - What is Contrast in Image Processing?
 - 图像增强综述
 - How to calculate the contrast of an image?
 - intensity_transformations
 
[OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡的更多相关文章
- [OpenCV实战]45 基于OpenCV实现图像哈希算法
		
目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...
 - [OpenCV实战]19 使用OpenCV实现基于特征的图像对齐
		
目录 1 背景 1.1 什么是图像对齐或图像对准? 1.2 图像对齐的应用 1.3 图像对齐基础理论 1.4 如何找到对应点 2 OpenCV的图像对齐 2.1 基于特征的图像对齐的步骤 2.2 代码 ...
 - [OpenCV实战]51 基于OpenCV实现图像极坐标变换与逆变换
		
在图像处理领域中,经常通过极坐标与笛卡尔直角坐标的互转来实现图像中圆形转为方形,或者通过极坐标反变换实现方形转圆形.例如钟表的表盘,人眼虹膜,医学血管断层都需要用到极坐标变换来实现圆转方. 文章目录 ...
 - [OpenCV实战]44 使用OpenCV进行图像超分放大
		
图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...
 - [OpenCV实战]34 使用OpenCV进行图像修复
		
目录 1 什么是图像修复 1.1 INPAINT_NS : Navier-Stokes based Inpainting 1.2 INPAINT_TELEA : Fast Marching Metho ...
 - [OpenCV实战]48 基于OpenCV实现图像质量评价
		
本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...
 - [OpenCV实战]52 在OpenCV中使用颜色直方图
		
颜色直方图是一种常见的图像特征,顾名思义颜色直方图就是用来反映图像颜色组成分布的直方图.颜色直方图的横轴表示像素值或像素值范围,纵轴表示该像素值范围内像素点的个数或出现频率.颜色直方图属于计算机视觉中 ...
 - [OpenCV实战]50 用OpenCV制作低成本立体相机
		
本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...
 - [OpenCV实战]31 使用OpenCV将一个三角形仿射变换到另一个三角形
		
目录 1 什么是仿射变换? 2 使用OpenCV进行三角形仿射变换 2.1 定义输入和输出 2.2 计算边界框 2.3 裁剪图像和更改坐标 2.4 计算仿射变换矩形 2.5 应用仿射变换到三角形 2. ...
 
随机推荐
- Spring bean装配流程和三级缓存
			
马士兵 源码方法论 不要忽略源码中的注释 先梳理脉络,再深入细节 大胆猜测.小心求证 见名知意 hold on 对源码有兴趣的都是变态 为了钱! Spring IoC Spring容器帮助管理对象,不 ...
 - 16.MongoDB系列之分片管理
			
1. 查看当前状态 1.1 查看配置信息 mongos> use config // 查看分片 mongos> db.shards.find() { "_id" : & ...
 - cURL error 1014: SSL verify failed 报错
			
报错 [ERROR] cURL error 1014: SSL verify failed (see https://curl.haxx.se/libcurl/c/libcurl-errors.htm ...
 - Laravel-Easy-Admin 快速搭建数据后台 web管理后台
			
基于PHP + Laravel + element-admin-ui 搭建的快速数据后台,只在解决系列后台增删改查等日常操作.快速搭建,在生成业务的同时可以花更多的时间关注技术本身,提高程序员自身进阶 ...
 - 前端框架Vue------>第一天学习(2) v-if
			
API:https://cn.vuejs.org/v2/api/#key 文章目录 5.条件渲染 5.1 . v-if 5.2 . v-else-if 6 .列表渲染 7 .事件监听 5.条件渲染 5 ...
 - SQL的表的连接Left Join / Right Join /inner join相关
			
Left Join / Right Join /inner join相关关于左连接和右连接总结性的一句话:左连接where只影向右表,右连接where只影响左表.Left Joinselect * f ...
 - 题解 P6745 『MdOI R3』Number
			
前言 不知道是不是正解但是觉得挺好理解. 科学计数法 将一个数表示为\(a\times 10^x\) 的形式.其中\(a\leq10\),\(x\) 为整数. \(\sf Solution\) 其实这 ...
 - 三十五、kubernetes NameSpace介绍
			
Kubernetes NameSpace 介绍 Kubernetes使用命名空间的概念帮助解决集群中在管理对象时的复杂性问题.命名空间允许将对象分组到一起,便于将它们作为一个单元进行筛选和控制.无论是 ...
 - JS 学习笔记(二)Ajax的简单使用
			
使用Ajax访问本地TXT文件 ajax.js // 创建请求对象 var ajax = new XMLHttpRequest(); // 建立连接 ajax.open('get', 'test.tx ...
 - Sql Server性能排查和优化懒人攻略
			
转载自作者zhang502219048的微信公众号[SQL数据库编程]:Sql Server性能排查和优化懒人攻略 很多年前,笔者那时刚从广东技术师范学院(现为广东技术师范大学,以前为广东民族学院)的 ...
 
			
		


















