首页 > 代码库 > OpenCV2马拉松第9圈——再谈对比度(对比度拉伸,直方图均衡化)

OpenCV2马拉松第9圈——再谈对比度(对比度拉伸,直方图均衡化)

收入囊中
  • lookup table
  • 对比度拉伸
  • 直方图均衡化
葵花宝典
lookup table是什么东西呢?
举个例子,假设你想把图像颠倒一下,f[i] = 255-f[i],你会怎么做?
for( int i = 0; i < I.rows; ++i)
  for( int j = 0; j < I.cols; ++j )
    I.at<uchar>(i,j) = 255 - I.at<uchar>(i,j);
大部分人应该都会这么做.或者:
for( i = 0; i < nRows; ++i){
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j){
            p[j] = 255 - p[j];
        }
}
或者使用迭代器
MatIterator_<uchar> it, end;
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = 255 - *it;


OpenCV提供了一个更快的方法,如下代码
LUT函数接收src,table和output
table是一个1*256的mat,将对应关系已经map好了
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;

Mat applyLookUp(const cv::Mat& image,const cv::Mat& lookup) { 
	Mat result;
	cv::LUT(image,lookup,result);
	return result;
}

int main( int, char** argv )
{
  	Mat image,gray;
  	image = imread( argv[1], 1 );
  	if( !image.data )
  		return -1;
  	cvtColor(image, gray, CV_BGR2GRAY);
    
	Mat lut(1,256,CV_8U);
	for (int i=0; i<256; i++) {
		lut.at<uchar>(i)= 255-i;
	}

	Mat out = applyLookUp(gray,lut);
	namedWindow("sample");
	imshow("sample",out);

  	waitKey(0);
  	return 0;
}

第一种:On-The-Fly RA93.7878 milliseconds
第二种:Efficient Way79.4717 milliseconds
第三种:Iterator83.7201 milliseconds
第四种:LUT function32.5759 milliseconds


对比度拉伸又是什么?先来直观地看张图片。


右边是原始图,可以发现,低灰度没有像素,但是左边就比较好,低灰度也有,这就是对比度的拉伸。

公式非常简单:f[i] = 255.0*(i-imin)/(imax-imin)+0.5);
当灰度 < imin , f[i] = 0;
当灰度 > imax, f[i] = 255;
imin < f[i] < imax,就线性映射.

有人就会问了,imin和imax要怎么确定呢?imin取10还是20还是30呢?
我们可以确定一个阀值minvalue,当灰度的个数>这个阀值minvalue时,就确定下来了。看下面这个确定的过程:
histSize[0]就是256
Mat hist= getHistogram(image);
int imin= 0;
for( ; imin < histSize[0]; imin++ )
    if (hist.at<float>(imin) > minValue)
        break;

int imax= histSize[0]-1;
for( ; imax >= 0; imax-- )
    if (hist.at<float>(imax) > minValue)
        break;

一旦imin,imax确定下来,我们就可以填充lookup table了
Mat lookup(1, 256, CV_8U);
for (int i=0; i<256; i++) {
    if (i < imin) lookup.at<uchar>(i)= 0;
    else if (i > imax) lookup.at<uchar>(i)= 255;
    else lookup.at<uchar>(i)= static_cast<uchar>(255.0*(i-imin)/(imax-imin)+0.5);
}


直方图均衡化
这也是一个老生长谈的问题了
我在http://blog.csdn.net/abcd1992719g/article/details/24357797#t4这里写过直方图均衡化
我再赘述一下
直方图均衡化的思想就是这样的。假设我有灰度级255的图像,但是都是属于[100,110]的灰度,图像对比度就很低,我应该尽可能拉到整个[0,255]
下面是直方图均衡化的代码,有个累积函数的概念,其实很简单。
我先计算出每个灰度级g(0),g(1)......g(255)点的个数,sum为图像width*height
那么累计函数c(0) = g(0)/sum
c(1) = (g(0)+g(1))/sum
......
c(255) = 1
下面是JAVA代码,改C++非常容易的
public int[][] Histogram_Equalization(int[][] oldmat)  
    {  
        int[][] new_mat = new int[height][width];  
        int[] tmp = new int[256];  
        for(int i = 0;i < width;i++){  
            for(int j = 0;j < height;j++){  
                //System.out.println(oldmat[j][i]);  
                int index = oldmat[j][i];  
                tmp[index]++;  
            }  
        }  
          
        float[] C = new float[256];  
        int total = width*height;  
        //计算累积函数  
        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 < width;i++){  
            for(int j = 0;j < height;j++){  
                new_mat[j][i] = (int)(C[oldmat[j][i]] * 255);  
                new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);  
                //System.out.println(new_mat[j][i]);  
            }  
        }  
        return new_mat;  
    }  





这是效果图,可以看到原来的图像被拉伸了

自适应直方图均衡化
AHE算法通过计算图像的局部直方图,然后重新分布亮度来来改变图像对比度。因此,该算法更适合于改进图像的局部对比度以及获得更多的图像细节。
想像以下一幅图像,左上角是黑乎乎的一团,但是其他区域很正常,如果只用HE,那么黑乎乎的那团是没法有多大改进的。
于是,你可以把那黑乎乎的一团当作一张图片,对那一部分进行HE,其实这就是AHE了,就是把图片分片处理,8*8是常用的选择。
然后,你就可以写一个循环来操作,算法和HE是一模一样的,当然可以工作,只是速度比较慢。
正如我下面代码所写的,利用双线性插值
我以前写CLAHE时候看的博客找不到了T_T   http://m.blog.csdn.net/blog/gududeyhc/8997009这里有但是远远没我以前看的那篇讲的清楚,如果你去看Pizer的论文估计要花很多的时间。下面是我用Java写的CLAHE.
CLAHE比AHE多了裁剪补偿的操作
/* 
     * 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;  
          
    }  

难道直方图均衡化的代码要让我们自己写?当然不是,下面就是API


初识API
C++: void equalizeHist(InputArray src, OutputArray dst)
 
  • src – Source 8-bit single channel image.
  • dst – Destination image of the same size and type as src .
TAT,这是目前为止最简单的 API了
内部好像不是用自适应直方图均衡化来做


荷枪实弹
先给出对比度拉伸的源代码
有一个我们上次用过的直方图类,加了一个拉伸的方法
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;

Mat applyLookUp(const cv::Mat& image,const cv::Mat& lookup) { 
	Mat result;
	cv::LUT(image,lookup,result);
	return result;
}

class Histogram1D {
private:
	int histSize[1]; // number of bins
	float hranges[2]; // min and max pixel value
	const float* ranges[1];
	int channels[1];

public:
	Histogram1D() {
		histSize[0]= 256;
		hranges[0]= 0.0;
		hranges[1]= 255.0;
		ranges[0]= hranges;
		channels[0]= 0; // by default, we look at channel 0
	}
	
	Mat getHistogram(const cv::Mat &image) {
		Mat hist;
		calcHist(&image,1,channels,Mat(),hist,1,histSize,ranges);
		return hist;
	}
	
	Mat getHistogramImage(const cv::Mat &image){
		Mat hist= getHistogram(image);
		double maxVal=0;
		double minVal=0;
		minMaxLoc(hist, &minVal, &maxVal, 0, 0);
		Mat histImg(histSize[0], histSize[0],CV_8U,Scalar(255));
		int hpt = static_cast<int>(0.9*histSize[0]);
		for( int h = 0; h < histSize[0]; h++ ) {
			float binVal = hist.at<float>(h);
			int intensity = static_cast<int>(binVal*hpt/maxVal);
			line(histImg,Point(h,histSize[0]),
			Point(h,histSize[0]-intensity),
			Scalar::all(0));
		}
		return histImg;
	}
	
	Mat stretch(const cv::Mat &image, int minValue=http://www.mamicode.com/0) {"white-space:pre">	Histogram1D h;
	Mat streteched = h.stretch(gray,100);
	namedWindow("sample");
	imshow("sample",streteched);
	
	namedWindow("histogram1");
	imshow("histogram1",h.getHistogramImage(gray));
	namedWindow("histogram2");
	imshow("histogram2",h.getHistogramImage(streteched));

  	waitKey(0);
  	return 0;
}

再来看直方图均衡化代码
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace cv;
using namespace std;

int main( int, char** argv )
{
  Mat src, dst;

  const char* source_window = "Source image";
  const char* equalized_window = "Equalized Image";

  /// Load image
  src = http://www.mamicode.com/imread( argv[1], 1 );>


举一反三
我在上面给出了CLAHE的JAVA代码
这是一个很好的学习材料,双线性插值加速,附带剪裁补偿
如果你有时间,应该认真去看看,这是当初花了一天的时间写的TAT



计算机视觉讨论群:162501053
转载请注明:http://blog.csdn.net/abcd1992719g