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);
}


直方圖均衡化
這也是一個老生長談的問題了
我再贅述一下
直方圖均衡化的思想就是這樣的。假設我有灰度級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=0) {
		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;

		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);
		}
		Mat result;
		result= applyLookUp(image,lookup);
		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);
    namedWindow("original");
	imshow("original",gray);
	
    	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 = imread( argv[1], 1 );

  if( !src.data )
    { cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
      return -1;
    }

  /// Convert to grayscale
  cvtColor( src, src, CV_BGR2GRAY );

  /// Apply Histogram Equalization
  equalizeHist( src, dst );

  /// Display results
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  namedWindow( equalized_window, CV_WINDOW_AUTOSIZE );

  imshow( source_window, src );
  imshow( equalized_window, dst );

  /// Wait until user exits the program
  waitKey(0);

  return 0;


舉一反三
我在上面給出了CLAHE的JAVA代碼
這是一個很好的學習材料,雙線性插值加速,附帶剪裁補償
如果你有時間,應該認真去看看,這是當初花了一天的時間寫的TAT



計算機視覺討論羣:162501053
轉載請註明:http://blog.csdn.net/abcd1992719g


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章