本文主要介绍基于图像强度变换算法来实现图像对比度均衡。通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值。本文主要通过OpenCV contrib中的intensity_transform模块实现图像对比度均衡。如果想了解具体相关方法原理见冈萨雷斯主编的图像处理经典书籍 数字图像处理Digital Image Processing 第四版第三章。
本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:
OpenCV_contrib库在windows下编译使用指南
本文所有代码见:
文章目录
- 1 相关知识介绍
- 1.1 图像强度
- 1.2 图像对比度
- 1.3 OpenCV中基于图像强度的对比度增强算法
- 2 代码与结果分析
- 2.1 调用接口说明
- 2.2 完整代码
- 2.3 测试与结果评价
- 2.3.1 测试结果
- 2.3.2 结果评价
- 3 参考
- 3.1 参考代码
- 3.2 参考文章
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