限制对比度自适应直方图均衡化(自我理解)

CLAHE算法对于医学图像,特别是医学红外图像的增强效果非常明显。

CLAHE  https://en.wikipedia.org/wiki/Adaptive_histogram_equalization

中文方面非常好的资料 限制对比度自适应直方图均衡化算法原理、实现及效果(我自己直接看解释没怎么看懂,不如直接看本篇博文下面的代码)

在OpenCV中已经实现了CLAHE,但是它在使用过程中,存在参数选择的问题。为了从根本上搞明白,我参考了网络上的一些代码

主要是来源 http://blog.youkuaiyun.com/abcd1992719g/article/details/25483395 (我觉的这篇博客的java代码更容易使人理解)

看完上面的java代码,我的理解

首先你得了解直方图均衡化(https://blog.youkuaiyun.com/u013066730/article/details/82969768)。

限制对比度自适应直方图均衡化方法步骤为:

将整幅图像切分为5*5的块block(但通常代码中为8*8,但本文以5*5举例),如图一右侧的黑色实线框所示。

                                                 图一

接着计算每个block的累积分布,在计算累积分布时,限制一下他的对比度,别让直方图出现特别陡峭的情况,直观图像就如图二所示。具体操作就是将直方图中高于某个阈值a的数值全都找出来构成一个集合B,然后B中的每一个元素b都变为a,其中b高于a的部分进行求和得到c,c除以256(该256是指灰度等级,也就是0-255)得到d,将d加在每一个灰度等级下,其实就是图二中蓝色尖状部分变为平坦状。

                                  图二

累积分布求好后,就需要将原始图像中的每个像素值通过累积分布映射为新的像素值。其中使用了双线性插值的方法,而在使用双线性插值时,需要将原图重新分配,也就是图一的粉色,绿色和紫色区域。其中粉色区域的像素值就直接使用对应block的累积分布直接计算即可得到新的像素值(这里如何计算可以参考直方图均衡化)。绿色区域的像素值a,使用绿色区域中邻近的2个block的累计分布计算得到全新的2个像素值b1,b2,然后根据a与这两个block的距离比例得到求和比例c1,1-c1,所以原始像素a对应的最终像素d = b1*c1+b2*(1-c1)。紫色区域的像素值p,使用紫色区域中邻近的4个block的累积分布计算得到全新的4个像素值q1,q2,q3,q4,然后像素p根据与这4个block的距离远近得到对应比例,第一个block的比例为u,v(就是图一中紫色方框叉距离第一个黑色实心框的x,y的比例),第二个block的比例为1-u,v,第三个block的比例为u,1-v,第四个block的比例为1-u,1-v,所以原始像素p对应的最终结果i=u*v*q1+(1-u)*v*q2+u*(1-v)*q3+(1-u)*(1-v)*q4。

哎,我这里说的也有点乱,我感觉不如直接花点时间看下面的代码,真是一看就懂。

/* 
     * CLAHE 
     * 自适应直方图均衡化 
     */  
    public int[][] AHE(int[][] oldmat,int pblock)  
    {  
        int block = pblock;  
        //将图像均匀分成等矩形大小,8行8列64个块是常用的选择  
        int width_block = width/block;  
        int height_block = height/block;  
          
        //存储各个直方图  
        int[][] tmp = new int[block*block][256];  
        //存储累积函数  
        float[][] C = new float[block*block][256];  
          
        //计算累积函数  
        for(int i = 0 ; i < block ; i ++)  
        {  
            for(int j = 0 ; j < block ; j++)  
            {  
                int start_x = i * width_block;  
                int end_x = start_x + width_block;  
                int start_y = j * height_block;  
                int end_y = start_y + height_block;  
                int num = i+block*j;  
                int total = width_block * height_block;  
                for(int ii = start_x ; ii < end_x ; ii++)  
                {  
                    for(int jj = start_y ; jj < end_y ; jj++)  
                    {  
                        int index = oldmat[jj][ii];  
                        tmp[num][index]++;  
                    }  
                }  
                  
                  
                //裁剪操作  
                int average = width_block * height_block / 255;  
                int LIMIT = 4 * average;  
                int steal = 0;  
                for(int k = 0 ; k < 256 ; k++)  
                {  
                    if(tmp[num][k] > LIMIT){  
                        steal += tmp[num][k] - LIMIT;  
                        tmp[num][k] = LIMIT;  
                    }  
                      
                }  
                  
                int bonus = steal/256;  
                //hand out the steals averagely  
                for(int k = 0 ; k < 256 ; k++)  
                {  
                    tmp[num][k] += bonus;  
                }  
                  
                //计算累积分布直方图  
                for(int k = 0 ; k < 256 ; k++)  
                {  
                    if( k == 0)  
                        C[num][k] = 1.0f * tmp[num][k] / total;  
                    else  
                        C[num][k] = C[num][k-1] + 1.0f * tmp[num][k] / total;  
                }  
                  
            }  
        }  
          
        int[][] new_mat = new int[height][width];  
        //计算变换后的像素值  
        //根据像素点的位置,选择不同的计算方法  
        for(int  i = 0 ; i < width; i++)  
        {  
            for(int j = 0 ; j < height; j++)  
            {  
                //four coners  
                if(i <= width_block/2 && j <= height_block/2)  
                {  
                    int num = 0;  
                    new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  
                }else if(i <= width_block/2 && j >= ((block-1)*height_block + height_block/2)){  
                    int num = block*(block-1);  
                    new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  
                }else if(i >= ((block-1)*width_block+width_block/2) && j <= height_block/2){  
                    int num = block-1;  
                    new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  
                }else if(i >= ((block-1)*width_block+width_block/2) && j >= ((block-1)*height_block + height_block/2)){  
                    int num = block*block-1;  
                    new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  
                }  
                  
                //four edges except coners  
                else if( i <= width_block/2 )  
                {  
                    //线性插值  
                    int num_i = 0;  
                    int num_j = (j - height_block/2)/height_block;  
                    int num1 = num_j*block + num_i;  
                    int num2 = num1 + block;  
                    float p =  (j - (num_j*height_block+height_block/2))/(1.0f*height_block);  
                    float q = 1-p;  
                    new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  
                }else if( i >= ((block-1)*width_block+width_block/2)){  
                    //线性插值  
                    int num_i = block-1;  
                    int num_j = (j - height_block/2)/height_block;  
                    int num1 = num_j*block + num_i;  
                    int num2 = num1 + block;  
                    float p =  (j - (num_j*height_block+height_block/2))/(1.0f*height_block);  
                    float q = 1-p;  
                    new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  
                }else if( j <= height_block/2 ){  
                    //线性插值  
                    int num_i = (i - width_block/2)/width_block;  
                    int num_j = 0;  
                    int num1 = num_j*block + num_i;  
                    int num2 = num1 + 1;  
                    float p =  (i - (num_i*width_block+width_block/2))/(1.0f*width_block);  
                    float q = 1-p;  
                    new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  
                }else if( j >= ((block-1)*height_block + height_block/2) ){  
                    //线性插值  
                    int num_i = (i - width_block/2)/width_block;  
                    int num_j = block-1;  
                    int num1 = num_j*block + num_i;  
                    int num2 = num1 + 1;  
                    float p =  (i - (num_i*width_block+width_block/2))/(1.0f*width_block);  
                    float q = 1-p;  
                    new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  
                }  
                  
                //inner area  
                else{  
                    int num_i = (i - width_block/2)/width_block;  
                    int num_j = (j - height_block/2)/height_block;  
                    int num1 = num_j*block + num_i;  
                    int num2 = num1 + 1;  
                    int num3 = num1 + block;  
                    int num4 = num2 + block;  
                    float u = (i - (num_i*width_block+width_block/2))/(1.0f*width_block);  
                    float v = (j - (num_j*height_block+height_block/2))/(1.0f*height_block);  
                    new_mat[j][i] = (int)((u*v*C[num4][oldmat[j][i]] +   
                            (1-v)*(1-u)*C[num1][oldmat[j][i]] +  
                            u*(1-v)*C[num2][oldmat[j][i]] +  
                            v*(1-u)*C[num3][oldmat[j][i]]) * 255);  
  
                }  
                  
                new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);         
            }  
        }  
          
        return new_mat;  
          
    }  

 

<think>嗯,用户这次问的是图像处理中的CLAHE算法,看来是对专业算法有研究需求。从引用内容看,用户可能正在学习图像增强技术,或者需要解决实际图像处理问题。用户提供的引用资料比较详细,但有些零散,需要我系统性地整合。 用户的问题很明确:要求解释CLAHE的原理和应用。不过从引用的代码片段看,用户可能还关心具体实现细节。引用[1]和[2]都提到传统直方图均衡化的局限性,这正好是CLAHE的改进点。引用[3]的OpenCV代码示例很有价值,说明用户可能需要编程指导。 我需要从基础概念讲起,但要注意层次。首先应该解释为什么需要CLAHE——传统直方图均衡化在局部区域效果差,全局调整会导致噪声放大。然后分三步说明原理:分块处理、对比度限制、双线性插值。应用部分要突出实用性,比如医学影像和遥感这些对局部对比度敏感的场景。 用户提供的参考文献显示他们可能看过一些资料,但不够系统。我注意到引用[2]提到“某些场合效果非常差”,这点很重要,应该在算法局限性里强调。代码示例虽然给出了参数设置,但没解释clipLimit和网格尺寸的选择依据,这部分需要补充说明。 在组织回答时,考虑把数学公式单独呈现,比如直方图均衡化的变换函数$s_k = T(r_k) = \sum_{j=0}^{k}\frac{n_j}{n}$。但要注意用户可能不是数学专业,需要搭配文字解释。对于双线性插值这样的空间算法,用文字描述可能不够直观,考虑在相关问题里建议可视化资源。 最后的应用建议要具体。引用[3]的代码里clipLimit=4是个典型值,但没说明调整规则,这点在回答里需要展开。另外注意到用户贴的代码最后做了二值化处理,这可能是个特殊应用场景,不过在核心原理部分先不涉及。</think>### 限制对比度自适应直方图均衡化(CLAHE)算法原理及应用 #### 一、算法原理 CLAHE 是对传统直方图均衡化(HE)的改进,通过**局部处理**和**对比度限制**解决 HE 的缺陷: 1. **传统 HE 的局限性** HE 通过全局变换函数 $s_k = T(r_k) = \sum_{j=0}^{k}\frac{n_j}{n}$ 增强对比度($r_k$ 为输入灰度,$n_j$ 为灰度级 $j$ 的像素数)。但会导致: - 局部区域对比度提升不足 - 噪声放大(尤其在均匀区域)[^2] 2. **自适应直方图均衡化(AHE)** - 将图像划分为 $M \times N$ 个小网格 - 对每个网格独立进行 HE,增强局部对比度 - **问题**:放大噪声,在平坦区域产生过度增强 3. **CLAHE 的核心改进** - **对比度限制** 预设裁剪阈值 $\beta$(如 clipLimit=4),限制每个网格直方图的高度。超出部分均匀分配到所有灰度级,确保变换函数斜率 $\frac{ds}{dr} \leq 1 + \beta$ *数学表达*: $$ h_{clip}(k) = \min(h(k), \beta) + \frac{\sum \max(h(k)-\beta, 0)}{K} $$ ($K$ 为灰度级数) - **双线性插值** 为避免网格边界伪影,对每个像素点的变换值由其周围 $4$ 个网格的变换函数插值得到: $$ s(x,y) = (1-a)(1-b)T_{ij}(r) + a(1-b)T_{i+1,j}(r) + (1-a)bT_{i,j+1}(r) + abT_{i+1,j+1}(r) $$ ($a,b$ 为相对位置权重) #### 二、应用场景 1. **医学影像**:增强 X 光、CT 的局部组织对比度,辅助病灶识别 2. **遥感图像**:提升地表纹理细节,如地质结构、植被分布 3. **低光照图像**:恢复暗区细节(如监控视频) 4. **计算机视觉**:作为特征提取的预处理步骤(如人脸检测) #### 三、代码实现(OpenCV 示例) ```python import cv2 import numpy as np # 读取图像并转为 Lab 色彩空间 bgr_img = cv2.imread("input.jpg") lab_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2Lab) l_channel, a_channel, b_channel = cv2.split(lab_img) # 创建 CLAHE 对象 clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(8,8)) # 关键参数 cl_channel = clahe.apply(l_channel) # 合并通道并转回 BGR enhanced_lab = cv2.merge((cl_channel, a_channel, b_channel)) result_img = cv2.cvtColor(enhanced_lab, cv2.COLOR_Lab2BGR) ``` **参数说明**: - `clipLimit`:对比度限制阈值(典型值 2-5) - `tileGridSize`:网格尺寸(如 (8,8) 表示 8×8 网格) #### 四、与传统 HE 效果对比 | 指标 | HE | CLAHE | |--------------|------------------|------------------| | 局部对比度 | 弱 | **强** | | 噪声控制 | 放大噪声 | **有效抑制** | | 均匀区域 | 过度增强 | **自然过渡** | | 计算复杂度 | 低 | 较高 | #### 五、局限性 - 需手动调整 `clipLimit` 和网格尺寸 - 高噪声图像仍需先降噪 - 边缘可能残留轻微块效应 > CLAHE 通过局部处理与对比度限制的平衡,显著提升了图像增强的实用性,成为医学影像、遥感等领域的标准预处理技术[^1][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值