• CLAHE的实现和研究


    CLAHE算法对于医学图像,特别是医学红外图像的增强效果非常明显。
    在OpenCV中已经实现了CLAHE,但是它在使用过程中,存在参数选择的问题。为了从根本上搞明白,我参考了网络上的一些代码
    实现了基于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;
    }
    原始图像
    GOCLAHE效果
    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;
    }
     
     
    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;
    }
     
    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不包括插值算法:
    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算法:
    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;
    }
    原始图像
    GOCLAHE效果
    OpenCV CLAHE效果
    从结果上来看,GOCLAHE方法和OpenCV提供的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
     
  • 相关阅读:
    设计模式-状态模式(25)
    设计模式-访问者模式(24)
    设计模式-观察者模式(22)
    设计模式-中介者模式(21)
    设计模式-行为型模式小结(20)
    设计模式-迭代器模式(19)
    Tomcat安装
    MySQL单表查询
    MySQL表操作
    MySQL表的完整性约束
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/16948038.html
Copyright © 2020-2023  润新知