zoukankan      html  css  js  c++  java
  • 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
     
  • 相关阅读:
    SQL INJECTION的SQL Server安全设置
    跨数据库查询
    IIS to secure
    win2003 服务器设置 完全版
    Taskkill命令详解
    PsExec
    Sql Server自增列处理
    Index Data
    Sql Server常用查询汇总
    Symbian S60 SDK模拟器自动退出的解决
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/6435601.html
Copyright © 2011-2022 走看看