CLAHE的实现和研究

CLAHE的实现和研究CLAHE 的实现和研究 CLAHE 算法对于医学图像 特别是医学红外图像的增强效果非常明显 CLAHE nbsp https en wikipedia org wiki Adaptive histogram equalization 中文方面非常好的资料限制对比度自适应直方图均衡化算法原理 实现及效果在 OpenCV 中已经实现了 CLAHE 但是它在使用过程中 存在参数选择的问题 为了从根本上搞明白 我参考了网络上的一些代码主要是来源 http blog csdn net abcd19

CLAHE的实现和研究

CLAHE算法对于医学图像,特别是医学红外图像的增强效果非常明显。
CLAHE  https://en.wikipedia.org/wiki/Adaptive_histogram_equalization
中文方面非常好的资料 限制对比度自适应直方图均衡化算法原理、实现及效果
在OpenCV中已经实现了CLAHE,但是它在使用过程中,存在参数选择的问题。为了从根本上搞明白,我参考了网络上的一些代码
主要是来源 http://blog.csdn.net/abcdg/article/details/
实现了基于OpenCV的CLAHE实现和研究。从最基本的开始做,分别实现HE算法,AHE算法,CLHE算法和CLAHE算法。素材分别采用了手部和手臂的红外图片,同时调用OpenCV生成代码和自己编写代码进行比对。
调用代码和实现效果:
int _tmain(
int argc, _TCHAR * argv[])

{
     //读入灰度的手部图像
    Mat src  = imread( “arm.jpg”, 0);
    Mat dst  = src.clone();
    Mat HT_OpenCV;
    Mat HT_GO;
    Mat AHE_GO;
    Mat CLHE_GO;
    Mat CLAHE_Without_Interpolation;
    Mat CLAHE_OpenCV;
    Mat CLAHE_GO;
    Mat matInter;
     OpenCV HT 方法
    cv : :equalizeHist(src,HT_OpenCV);
     GO HT方法
    HT_GO  = eaualizeHist_GO(src);
     GO AHE方法
    AHE_GO  = aheGO(src);
     GO CLHE方法
    CLHE_GO  = clheGO(src);
     clahe不计算差值
    CLAHE_Without_Interpolation  = claheGoWithoutInterpolation(src);
     OpenCV CLAHE 方法
    Ptr <cv : :CLAHE > clahe  = createCLAHE(); //默认参数
    clahe – >apply(src, CLAHE_OpenCV);
     GO CLAHE方法
    CLAHE_GO  = claheGO(src);
 
     结果显示
    imshow( “原始图像”,src);
    imshow( “OpencvHT”,HT_OpenCV);
    imshow( “GOHT”,HT_GO);
    imshow( “GOAHE”,AHE_GO);
    imshow( “GOCLHE”,CLHE_GO);
    imshow( “GOCLAHE”,CLAHE_GO);
    imshow( “CLAHE_Without_Interpolation”,CLAHE_Without_Interpolation);
    imshow( “OpencvCLAHE”,CLAHE_OpenCV);
    waitKey();
     return  0;
}







































CLAHE的实现和研究
原始图像
CLAHE的实现和研究
GOCLAHE效果
CLAHE的实现和研究
OpenCV CLAHE效果
HE算法: Mat eaualizeHist_GO(Mat src)
{

     int width  = src.cols;
     int height = src.rows;
    Mat HT_GO  = src.clone();
     int tmp[ 256]  ={ 0};
     float C[ 256]  = { 0. 0};
     int total  = width *height;  
     for ( int i = 0 ;i <src.rows;i ++)
    {
         for ( int j = 0;j <src.cols;j ++)
        {
             int index  = src.at <uchar >(i,j);
            tmp[index]  ++;
        }
    }
     //计算累积函数  
     for( int i  =  0;i  <  256 ; i ++){  
         if(i  ==  0)  
            C[i]  =  1.0f  * tmp[i]  / total;  
         else  
            C[i]  = C[i – 1]  +  1.0f  * tmp[i]  / total;  
    }  
     //这里的累积函数分配的方法非常直观高效
     for( int i  =  0;i  < src.rows;i ++){  
         for( int j  =  0;j  < src.cols;j ++){      
             int index  = src.at <uchar >(i,j);
            HT_GO.at <uchar >(i,j)  = C[index]  *  255  ;
        }  
    }  
     return HT_GO;
}





























CLAHE的实现和研究
CLAHE的实现和研究
 
AHE算法:
Mat aheGO(Mat src,
int _step  =  8)

{
    Mat AHE_GO  = src.clone();
     int block  = _step;
     int width  = src.cols;
     int height  = src.rows;
     int width_block  = width /block;  //每个小格子的长和宽
     int height_block  = height /block;
     //存储各个直方图  
     int tmp2[ 8 * 8][ 256]  ={ 0};
     float C2[ 8 * 8][ 256]  = { 0. 0};
     //分块
     int total  = width_block  * height_block; 
     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;  
             //遍历小块,计算直方图
             for( int ii  = start_x ; ii  < end_x ; ii ++)  
            {  
                 for( int jj  = start_y ; jj  < end_y ; jj ++)  
                {  
                     int index  =src.at <uchar >(jj,ii);
                    tmp2[num][index] ++;  
                }  
            } 
             //计算累积分布直方图  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                 if( k  ==  0)  
                    C2[num][k]  =  1.0f  * tmp2[num][k]  / total;  
                 else  
                    C2[num][k]  = C2[num][k – 1]  +  1.0f  * tmp2[num][k]  / total;  
            }  
        }
    }
     //将统计结果写入
     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;  
             //遍历小块,计算直方图
             for( int ii  = start_x ; ii  < end_x ; ii ++)  
            {  
                 for( int jj  = start_y ; jj  < end_y ; jj ++)  
                {  
                     int index  =src.at <uchar >(jj,ii);
                     //结果直接写入AHE_GO中去
                    AHE_GO.at <uchar >(jj,ii)  = C2[num][index]  *  255  ;
                }  
            } 
        }
    }
     return AHE_GO;
}































































CLAHE的实现和研究
CLHE算法:
//这里是在全局直方图加入“限制对比度”方法

Mat clheGO(Mat src, int _step  =  8)
{
     int width  = src.cols;
     int height = src.rows;
    Mat CLHE_GO  = src.clone();
     int tmp[ 256]  ={ 0};
     float C[ 256]  = { 0. 0};
     int total  = width *height;  
     for ( int i = 0 ;i <src.rows;i ++)
    {
         for ( int j = 0;j <src.cols;j ++)
        {
             int index  = src.at <uchar >(i,j);
            tmp[index]  ++;
        }
    }
     /限制对比度计算部分,注意这个地方average的计算不一定科学
     int average  = width  * height  /  255 / 64;  
     int LIMIT  =  4  * average;  
     int steal  =  0;  
     for( int k  =  0 ; k  <  256 ; k ++)  
    {  
         if(tmp[k]  > LIMIT){  
            steal  += tmp[k]  – LIMIT;  
            tmp[k]  = LIMIT;  
        }  
    }  
     int bonus  = steal / 256;  
     //hand out the steals averagely  
     for( int k  =  0 ; k  <  256 ; k ++)  
    {  
        tmp[k]  += bonus;  
    }  
     ///
     //计算累积函数  
     for( int i  =  0;i  <  256 ; i ++){  
         if(i  ==  0)  
            C[i]  =  1.0f  * tmp[i]  / total;  
         else  
            C[i]  = C[i – 1]  +  1.0f  * tmp[i]  / total;  
    }  
     //这里的累积函数分配的方法非常直观高效
     for( int i  =  0;i  < src.rows;i ++){  
         for( int j  =  0;j  < src.cols;j ++){      
             int index  = src.at <uchar >(i,j);
            CLHE_GO.at <uchar >(i,j)  = C[index]  *  255  ;
        }  
    }  
     return CLHE_GO;
}

















































CLAHE的实现和研究
CLAHE不包括插值算法:
Mat claheGoWithoutInterpolation(Mat src, 
int _step  =  8)

{
    Mat CLAHE_GO  = src.clone();
     int block  = _step; //pblock
     int width  = src.cols;
     int height = src.rows;
     int width_block  = width /block;  //每个小格子的长和宽
     int height_block  = height /block;
     //存储各个直方图  
     int tmp2[ 8 * 8][ 256]  ={ 0};
     float C2[ 8 * 8][ 256]  = { 0. 0};
     //分块
     int total  = width_block  * height_block; 
     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;  
             //遍历小块,计算直方图
             for( int ii  = start_x ; ii  < end_x ; ii ++)  
            {  
                 for( int jj  = start_y ; jj  < end_y ; jj ++)  
                {  
                     int index  =src.at <uchar >(jj,ii);
                    tmp2[num][index] ++;  
                }  
            } 
             //裁剪和增加操作,也就是clahe中的cl部分
             //这里的参数 对应《Gem》上面 fCliplimit  = 4  , uiNrBins  = 255
             int average  = width_block  * height_block  /  255;  
             int LIMIT  =  4  * average;  
             int steal  =  0;  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                 if(tmp2[num][k]  > LIMIT){  
                    steal  += tmp2[num][k]  – LIMIT;  
                    tmp2[num][k]  = LIMIT;  
                }  
            }  
             int bonus  = steal / 256;  
             //hand out the steals averagely  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                tmp2[num][k]  += bonus;  
            }  
             //计算累积分布直方图  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                 if( k  ==  0)  
                    C2[num][k]  =  1.0f  * tmp2[num][k]  / total;  
                 else  
                    C2[num][k]  = C2[num][k – 1]  +  1.0f  * tmp2[num][k]  / total;  
            }  
        }
    }
     //计算变换后的像素值  
     //将统计结果写入
     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;  
             //遍历小块,计算直方图
             for( int ii  = start_x ; ii  < end_x ; ii ++)  
            {  
                 for( int jj  = start_y ; jj  < end_y ; jj ++)  
                {  
                     int index  =src.at <uchar >(jj,ii);
                     //结果直接写入AHE_GO中去
                    CLAHE_GO.at <uchar >(jj,ii)  = C2[num][index]  *  255  ;
                }  
            } 
        }
    
     }  
     return CLAHE_GO;
}



















































































CLAHE的实现和研究
CLAHE算法:

Mat claheGO(Mat src,
int _step  =  8)

{
    Mat CLAHE_GO  = src.clone();
     int block  = _step; //pblock
     int width  = src.cols;
     int height = src.rows;
     int width_block  = width /block;  //每个小格子的长和宽
     int height_block  = height /block;
     //存储各个直方图  
     int tmp2[ 8 * 8][ 256]  ={ 0};
     float C2[ 8 * 8][ 256]  = { 0. 0};
     //分块
     int total  = width_block  * height_block; 
     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;  
             //遍历小块,计算直方图
             for( int ii  = start_x ; ii  < end_x ; ii ++)  
            {  
                 for( int jj  = start_y ; jj  < end_y ; jj ++)  
                {  
                     int index  =src.at <uchar >(jj,ii);
                    tmp2[num][index] ++;  
                }  
            } 
             //裁剪和增加操作,也就是clahe中的cl部分
             //这里的参数 对应《Gem》上面 fCliplimit  = 4  , uiNrBins  = 255
             int average  = width_block  * height_block  /  255;  
             //关于参数如何选择,需要进行讨论。不同的结果进行讨论
             //关于全局的时候,这里的这个cl如何算,需要进行讨论 
             int LIMIT  =  40  * average;  
             int steal  =  0;  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                 if(tmp2[num][k]  > LIMIT){  
                    steal  += tmp2[num][k]  – LIMIT;  
                    tmp2[num][k]  = LIMIT;  
                }  
            }  
             int bonus  = steal / 256;  
             //hand out the steals averagely  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                tmp2[num][k]  += bonus;  
            }  
             //计算累积分布直方图  
             for( int k  =  0 ; k  <  256 ; k ++)  
            {  
                 if( k  ==  0)  
                    C2[num][k]  =  1.0f  * tmp2[num][k]  / total;  
                 else  
                    C2[num][k]  = C2[num][k – 1]  +  1.0f  * tmp2[num][k]  / total;  
            }  
        }
    }
     //计算变换后的像素值  
     //根据像素点的位置,选择不同的计算方法  
     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;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)(C2[num][CLAHE_GO.at <uchar >(j,i)]  *  255);  
            } else  if(i  < = width_block / 2  && j  > = ((block – 1) *height_block  + height_block / 2)){  
                 int num  = block *(block – 1);  
                CLAHE_GO.at <uchar >(j,i)  = ( int)(C2[num][CLAHE_GO.at <uchar >(j,i)]  *  255);  
            } else  if(i  > = ((block – 1) *width_block +width_block / 2)  && j  < = height_block / 2){  
                 int num  = block – 1;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)(C2[num][CLAHE_GO.at <uchar >(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;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)(C2[num][CLAHE_GO.at <uchar >(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;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)((q *C2[num1][CLAHE_GO.at <uchar >(j,i)] + p *C2[num2][CLAHE_GO.at <uchar >(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;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)((q *C2[num1][CLAHE_GO.at <uchar >(j,i)] + p *C2[num2][CLAHE_GO.at <uchar >(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;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)((q *C2[num1][CLAHE_GO.at <uchar >(j,i)] + p *C2[num2][CLAHE_GO.at <uchar >(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;  
                CLAHE_GO.at <uchar >(j,i)  = ( int)((q *C2[num1][CLAHE_GO.at <uchar >(j,i)] + p *C2[num2][CLAHE_GO.at <uchar >(j,i)]) *  255);  
            }  
             //双线性插值
             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);  
                CLAHE_GO.at <uchar >(j,i)  = ( int)((u *v *C2[num4][CLAHE_GO.at <uchar >(j,i)]  +   
                    ( 1 -v) *( 1 -u) *C2[num1][CLAHE_GO.at <uchar >(j,i)]  +  
                    u *( 1 -v) *C2[num2][CLAHE_GO.at <uchar >(j,i)]  +  
                    v *( 1 -u) *C2[num3][CLAHE_GO.at <uchar >(j,i)])  *  255);  
            }  
             //最后这步,类似高斯平滑
            CLAHE_GO.at <uchar >(j,i)  = CLAHE_GO.at <uchar >(j,i)  + (CLAHE_GO.at <uchar >(j,i)  <<  8)  + (CLAHE_GO.at <uchar >(j,i)  <<  16);         
        }  
    }  
   return CLAHE_GO;
}













































































































































原始图像
CLAHE的实现和研究
GOCLAHE效果
CLAHE的实现和研究
OpenCV CLAHE效果
从结果上来看,GOCLAHE方法和OpenCV提供的CLAHE方法是一样的。
再放一组图片
CLAHE的实现和研究
CLAHE的实现和研究
CLAHE的实现和研究
CLAHE的实现和研究
CLAHE的实现和研究
代码实现之后,留下两个问题:
集中在这段代码
            //这里的参数 对应《Gem》上面 fCliplimit  = 4  , uiNrBins  = 255

             int average  = width_block  * height_block  /  255;  
             //关于参数如何选择,需要进行讨论。不同的结果进行讨论
             //关于全局的时候,这里的这个cl如何算,需要进行讨论 
             int LIMIT  =  40  * average;  
             int steal  =  0;  




1、在进行CLAHE中CL的计算,也就是限制对比度的计算的时候,参数的选择缺乏依据。在原始的《GEMS》中提供的参数中,  fCliplimit  = 4  , uiNrBins  = 255. 但是在OpenCV的默认参数中,这里是40.就本例而言,如果从结果上反推,我看10比较好。这里参数的选择缺乏依据;
2、CLHE是可以用来进行全局直方图增强的,那么这个时候,这个 average 如何计算,肯定不是width * height/255,这样就太大了,算出来的LIMIT根本没有办法获得。
但是就实现血管增强的效果而言,这些结果是远远不够的。一般来说,对于CLAHE计算出来的结果,进行Frangi增强或者使用超分辨率增强?结果就是要把血管区域强化出来。
p.s:
arm.jpg 和 hand.jpg
CLAHE的实现和研究
CLAHE的实现和研究
 
目前方向:图像拼接融合、图像识别 联系方式:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232328.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 【UR #3】链式反应

    【UR #3】链式反应

    2021年7月5日
    80
  • Android 使用ViewPager实现左右循环滑动图片

    Android 使用ViewPager实现左右循环滑动图片ViewPager这个小demo实现的是可以左右循环滑动图片,下面带索引,滑到最后一页在往右滑动就要第一页,第一页往左滑动就到最后一页,先上效果图,用美女图片是我一贯的作风,呵呵1.  首先看一些layout下的xml

    2022年7月22日
    11
  • STM32 JLINK接口定义 JTAG/SWD「建议收藏」

    STM32 JLINK接口定义 JTAG/SWD「建议收藏」

    2022年5月8日
    66
  • oracle恢复删除的数据时长_oracle 闪回查询语句

    oracle恢复删除的数据时长_oracle 闪回查询语句oracle提供了针对已经删除的数据恢复;分为两种方法:scn和时间戳两种方法恢复。

    2025年11月19日
    6
  • 软件安装 —— 使用官方ODT定制安装Office

    软件安装 —— 使用官方ODT定制安装Office前言正在MacOS11的BigSur如火如荼的宣传着,我这不争气的Windows系统也终于越看越是臃肿,做为生产力工具,第一还是要稳定精简,一气之下,直接换上了Windows特别版本LTSC,据说这个版本特别特别稳定,很长时间才会更新一次,但用过的都说好,想想还是激动的。重装系统是一个重大工程,那做为Windows第二重要的软件Office目前的安装模式要么官方下载安装包要么离线部署,而官方的安装包可谓是一键超级全家桶搬进C盘,有用的没用的全部装一遍,最终还是靠离线部署来安装了,可以定制想要的软件,还

    2025年10月11日
    2
  • web前端零基础该怎么学习呢?「建议收藏」

    由于前端开发的火热和一些IT巨头公司对 web前端开发人员的需求旺盛,让越来越多的人转入前端。前端开发领域是IT技术语言领域唯一一个男女老少都可以快速入门并快速提升兴趣的领域,今天就来聊聊前端到底该怎么学~话不多说,让我们直接进入今天的主题。web前端的基本工作职责和基础技能(要清楚)web前端的分类和门派(简要概述,武林实在是太大啦)。前端开发必看的书籍资料(干货重点)。如果…

    2022年4月12日
    47

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号