【fishing-pan:https://blog.csdn.net/u013921430 转载请注明出处】

  提到图像增强,第一印象就是直方图均衡与直方图规定化,这是最常见的也是非常有效的全局图像增强方法。在前不久的一次组会讨论中,课题组的一位同学提到了“自适应图像增强”,虽然自己以前也用过,但是一时间忘记了原理,就去复习了一下,其实他使用的方法的全称应该叫自适应直方图均衡,对应的是Matlab 中的adapthisteq 函数;

  我在复习的过程中,偶然发现了另一个图像增强的算法,也就是这篇文章中要提到的——自适应对比度增强(Adaptive Contrast Enhancement,ACE),下面就从原理到实现,来好好聊一下这种方法。

自适应对比度增强

  在图像处理的方法中,自适应方法是与图像本身信息相关,根据图像对图特征对图像进行处理的一系列方法,这些方法往往具有更好的鲁棒性、普适性。而本文中提到的这种ACE方法由 N a r e n d r a P M Narendra P M NarendraPM等人在《Real-Time Adaptive Contrast Enhancement》中提到,原理简单易懂,有兴趣的朋友可以点击链接去阅读。

   对于图像中的每一个点,分别计算其局部均值与局部标准差;
M ( i , j ) = 1 ( 2 n + 1 ) ( 2 m + 1 ) ∑ s = i − n i + n ∑ k = j − m j + m f ( s , k ) M(i,j)=\frac{1}{(2n+1)(2m+1)}\sum_{s=i-n}^{i+n}\sum_{k=j-m}^{j+m}f(s,k) M(i,j)=(2n+1)(2m+1)1s=ini+nk=jmj+mf(s,k)
σ 2 ( i , j ) = 1 ( 2 n + 1 ) ( 2 m + 1 ) ∑ s = i − n i + n ∑ k = j − m j + m ( f ( s , k ) − M ( i , j ) ) 2 \sigma ^{2}(i,j)=\frac{1}{(2n+1)(2m+1)}\sum_{s=i-n}^{i+n}\sum_{k=j-m}^{j+m}(f(s,k)-M(i,j))^{2} σ2(i,j)=(2n+1)(2m+1)1s=ini+nk=jmj+m(f(s,k)M(i,j))2
  上述式子中, f ( s , k ) f(s,k) f(s,k)代表坐标为 ( s , k ) (s,k) (s,k)的点的像素值, M ( i , j ) M(i,j) M(i,j)为以点 ( i , j ) (i,j) (i,j)为中心,窗口大小为 [ ( 2 n + 1 ) , ( 2 m + 1 ) ] [(2n+1),(2m+1)] [(2n+1)(2m+1)]的区域的局部均值,对应的 σ 2 ( i , j ) \sigma ^{2}(i,j) σ2(i,j)为局部的方差, σ ( i , j ) \sigma (i,j) σ(i,j)为局部图像的标准差。
  
  在求得局部均值与标准差后,就可以对图像进行增强了,具体的公式如下;
I ( i , j ) = M ( i , j ) + G ( f ( i , j ) − M ( i , j ) ) I(i,j)=M(i,j)+G(f(i,j)-M(i,j)) I(i,j)=M(i,j)+G(f(i,j)M(i,j))
G = α M σ ( i , j ) 0 < α < 1 G=\alpha \frac{M}{\sigma (i,j)}\qquad 0<\alpha<1 G=ασ(i,j)M0<α<1
  上式中, I ( i , j ) I(i,j) I(i,j)为增强后的像素值, M M M为全局均值(你也可以把它设为某一合理数值), α \alpha α是一个系数参数,一般取小于1大于0的小数。

  再来分析一下,上面式子的含义;如果将每个点的局部均值 M ( i , j ) M(i,j) M(i,j)构成一张图,其实就是均值滤波的结果,而在《数字图像傅里叶变换的物理意义及简单应用》中,我提到过均值滤波是一种低通滤波,获得的是图像的低频部分,也就是背景部分, f ( i , j ) − M ( i , j ) f(i,j)-M(i,j) f(i,j)M(i,j)就可以用来量化图像中的一个点是高频还是低频。而在一般情况下, G G G 都是大于1的,所以通过 G ( f ( i , j ) − M ( i , j ) ) G(f(i,j)-M(i,j)) G(f(i,j)M(i,j))可以实现对图像的高频部分的放大,进而对图像进行增强。
在这里插入图片描述
  再来看看参数 G G G,经过上面的过程可以看出,如果 G G G 是一个固定参数,比如都取5,通过式子中的局部均值,我们已经能够将图像实现一定程度上的自适应增强了。那么为什么还要在参数G中引入标准差呢?

  我们回忆一下对比度增强的初衷,对比度增强是为了让本身对比度不强的图像的对比度变得明显,而对本身对比度很强的图像,是没必要做增强的。那么在同一图像中,我们尤其需要增强对比度不强的部分。而方差表示的是图像的像素值的均匀性,我们可以认为方差越大的局部区域,其像素值越不均匀,对比度越强;反之,方差越小的局部区域,其像素值越均匀,对比度越弱。因此,在参数 G G G中除以了局部标准差,可以让图像中对比度较弱的部分的增强效果更加明显。

  其次,如果对整张图像中所有点进行等比例增强,图像中本身就是高频的部分出现过增强的现象,图像看起来十分奇怪。

彩色图像的ACE

  在网上看到有人说,对彩色图像增强可以分别对RGB三通道进行增强后进行合并。这个观点是错误的,因为分别对各个通道进行增强,会引起图像色相的变化,图像会变的不是其原来的颜色了。

  所以需要将图像转到HSI色彩空间,或者是YCrCb颜色空间。前者只需要对I亮度通道进行增强,而H、S分别代表的色调和饱和度通道不需要变化。后者用Y通道表示亮度,只需要对Y通道进行增强即可。增强之后再合并通道,转换回RGB空间便完成了对彩色图像的增强。

代码

  在这里我只提供一个adaptContrastEnhancement函数的代码,完整的代码也很简单,可以点击下载。无C币的可以直接在博客留言,我也会发送到你的邮箱。

C++ 版本代码

//--------------------------
//Adaptive Contrast Enhancement(自适应对比度增强,ACE)
//不用先生,2018.11.08
//
//函数功能:获取图像的局部均值与局部标准差的图
//函数名称:adaptContrastEnhancement
//函数参数:Mat &scr:输入图像,为三通道RGB图像;
//函数参数:Mat &dst:增强后的输出图像,为三通道RGB图像;
//函数参数:int winSize:局部均值的窗口大小,应为单数;
//函数参数:int maxCg:增强幅度的上限;
//返回类型:bool
//--------------------

bool adaptContrastEnhancement(Mat &scr, Mat &dst, int winSize,int maxCg)
{
	if (!scr.data)  //判断图像是否被正确读取;
	{
		cerr << "自适应对比度增强函数读入图片有误";
		return false;
	}

	Mat ycc;                        //转换空间到YCrCb;
	cvtColor(scr, ycc, COLOR_RGB2YCrCb);

	vector<Mat> channels(3);        //分离通道;
	split(ycc, channels);

	
	Mat localMeansMatrix(scr.rows , scr.cols , CV_32FC1);
	Mat localVarianceMatrix(scr.rows , scr.cols , CV_32FC1);
	
	if (!getVarianceMean(channels[0], localMeansMatrix, localVarianceMatrix, winSize))   //对Y通道进行增强;
	{
		cerr << "计算图像均值与标准差过程中发生错误";
		return false;
	}

	Mat temp = channels[0].clone();

	Scalar  mean;
	Scalar  dev;
	meanStdDev(temp, mean, dev);

	float meansGlobal = mean.val[0];
	Mat enhanceMatrix(scr.rows, scr.cols, CV_8UC1);

	for (int i = 0; i < scr.rows; i++)            //遍历,对每个点进行自适应调节
	{
		for (int j = 0; j < scr.cols; j++)
		{
			if (localVarianceMatrix.at<float>(i, j) >= 0.01)
			{
				float cg = 0.2*meansGlobal / localVarianceMatrix.at<float>(i, j);
				float cgs = cg > maxCg ? maxCg : cg;
				cgs = cgs < 1 ? 1 : cgs;
				
				int e = localMeansMatrix.at<float>(i, j) + cgs* (temp.at<uchar>(i, j) - localMeansMatrix.at<float>(i, j));
				if (e > 255){ e = 255; }
				else if (e < 0){ e = 0; }
				enhanceMatrix.at<uchar>(i, j) = e;
			}
			else
			{
				enhanceMatrix.at<uchar>(i, j) = temp.at<uchar>(i, j);
			}
		}
			
	}
	
	channels[0] = enhanceMatrix;    //合并通道,转换颜色空间回到RGB
	merge(channels, ycc);

	cvtColor(ycc, dst, COLOR_YCrCb2RGB);
		
}

Python 版本代码(2020.03.11)

  最近有不少人私信我,问我有没有python版本的代码,所以我晚上写了一份;完整的脚本都在这里了,里面提供了两种求均值方差的方法!

# -*- coding: utf-8 -*-
"""
Created on Wed Mar 11 20:20:47 2020

@author: 不用先生
"""
import numpy as np
import cv2

def getVarianceMean(scr, winSize):
    if scr is None or winSize is None:
        print("The input parameters of getVarianceMean Function error")
        return -1
    
    if winSize % 2 == 0:
        print("The window size should be singular")
        return -1 
    
    copyBorder_map=cv2.copyMakeBorder(scr,winSize//2,winSize//2,winSize//2,winSize//2,cv2.BORDER_REPLICATE)
    shape=np.shape(scr)
    
    local_mean=np.zeros_like(scr)
    local_std=np.zeros_like(scr)
    
    for i in range(shape[0]):
        for j in range(shape[1]):   
            temp=copyBorder_map[i:i+winSize,j:j+winSize]
            local_mean[i,j],local_std[i,j]=cv2.meanStdDev(temp)
            if local_std[i,j]<=0:
                local_std[i,j]=1e-8
            
    return local_mean,local_std
    
def adaptContrastEnhancement(scr, winSize, maxCg):
    if scr is None or winSize is None or maxCg is None:
        print("The input parameters of ACE Function error")
        return -1
    
    YUV_img=cv2.cvtColor(scr,cv2.COLOR_BGR2YUV)    ##转换通道
    Y_Channel = YUV_img[:,:,0]
    shape=np.shape(Y_Channel)
    
    meansGlobal=cv2.mean(Y_Channel)[0]
    
    ##这里提供使用boxfilter 计算局部均质和方差的方法
#    localMean_map=cv2.boxFilter(Y_Channel,-1,(winSize,winSize),normalize=True)
#    localVar_map=cv2.boxFilter(np.multiply(Y_Channel,Y_Channel),-1,(winSize,winSize),normalize=True)-np.multiply(localMean_map,localMean_map)
#    greater_Zero=localVar_map>0
#    localVar_map=localVar_map*greater_Zero+1e-8
#    localStd_map = np.sqrt(localVar_map)
   
    localMean_map, localStd_map=getVarianceMean(Y_Channel,winSize)

    for i in range(shape[0]):
        for j in range(shape[1]):
            
            cg = 0.2*meansGlobal/ localStd_map[i,j];
            if cg >maxCg:
                cg=maxCg
            elif cg<1:
                cg=1
            
            temp = Y_Channel[i,j].astype(float)
            temp=max(0,min(localMean_map[i,j]+cg*(temp-localMean_map[i,j]),255))
            
#            Y_Channel[i,j]=max(0,min(localMean_map[i,j]+cg*(Y_Channel[i,j]-localMean_map[i,j]),255))
            Y_Channel[i,j]=temp
                
            
    YUV_img[:,:,0]=Y_Channel
    
    dst=cv2.cvtColor(YUV_img,cv2.COLOR_YUV2BGR)
    
    return dst

def main():
    img=cv2.imread(input_fn)
    
    if img is None:
        print("The file name error,please check it")
        return -1
    
    print(np.shape(img))
    dstimg=adaptContrastEnhancement(img,15,10)
    
    cv2.imwrite('output.jpg',dstimg)
    cv2.waitKey(0)
    
    return 0
 
    
input_fn='temp1.jpg'
if __name__ == '__main__':
    main()


运行结果

在这里插入图片描述
在这里插入图片描述

  可以明显看出,图像中原先对比度不强的部分,如树干、江面、远景。经过增强后,都有了明显的改善,而本身对比度已经明显的区域,就没有太大的改变。

已完。。

参考

  1. Narendra P M, Fitch R C. Real-time adaptive contrast enhancement[J]. IEEE transactions on pattern analysis and machine intelligence, 1981 (6): 655-661.
  2. http://www.cnblogs.com/Imageshop/p/3324282.html
GitHub 加速计划 / opencv31 / opencv
77.37 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:2 个月前 )
ee95bfe2 Use generic SIMD in warpAffineBlocklineNN 1 天前
ddc03c07 Disable SME2 branches in KleidiCV as it's incompatible with some CLang versions, e.g. NDK 28b1 1 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐